Skip to content

Commit 7fcad8a

Browse files
committed
agent-config: update ts sdk to expose agent config
1 parent 4406744 commit 7fcad8a

File tree

18 files changed

+599
-10
lines changed

18 files changed

+599
-10
lines changed

sdks/ts/packages/golem-ts-sdk/src/decorators/agent.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ import {
1919
AgentMode,
2020
Principal,
2121
Snapshotting,
22+
ConfigKeyValueType,
23+
ConfigValueType,
2224
} from 'golem:agent/common';
2325
import { ResolvedAgent } from '../internal/resolvedAgent';
24-
import { TypeMetadata } from '@golemcloud/golem-ts-types-core';
26+
import { ConstructorArg, TypeMetadata } from '@golemcloud/golem-ts-types-core';
2527
import {
2628
getNewPhantomRemoteClient,
2729
getPhantomRemoteClient,
@@ -42,6 +44,8 @@ import { validateHttpMount } from '../internal/http/validation';
4244
import { getAgentConstructorSchema } from '../internal/schema/constructor';
4345
import { getAgentMethodSchema } from '../internal/schema/method';
4446
import ms from 'ms';
47+
import { fromTsType } from '../internal/mapping/types/WitType';
48+
import { TypeScope } from '../internal/mapping/types/scope';
4549

4650
export type SnapshottingOption = 'disabled' | 'enabled' | { periodic: string } | { every: number };
4751

@@ -281,6 +285,11 @@ export function agent(options?: AgentDecoratorOptions) {
281285
validateHttpMount(agentClassName.value, httpMount, constructor);
282286
}
283287

288+
const agentConfigEntries = Either.getOrThrowWith(
289+
getAgentConfigEntries(classMetadata.constructorArgs),
290+
err => new Error(`Failed to describe agent config: ${err}`),
291+
);
292+
284293
const agentType: AgentType = {
285294
typeName: agentTypeName.value,
286295
description: agentTypeDescription,
@@ -290,7 +299,7 @@ export function agent(options?: AgentDecoratorOptions) {
290299
mode: options?.mode ?? 'durable',
291300
httpMount,
292301
snapshotting: resolveSnapshotting(options?.snapshotting),
293-
config: [],
302+
config: agentConfigEntries,
294303
};
295304

296305
AgentTypeRegistry.register(agentClassName, agentType);
@@ -370,3 +379,31 @@ export function agent(options?: AgentDecoratorOptions) {
370379
});
371380
};
372381
}
382+
383+
function getAgentConfigEntries(constructorParameters: ConstructorArg[]): Either.Either<ConfigKeyValueType[], string> {
384+
const entries: ConfigKeyValueType[] = [];
385+
386+
for (const param of constructorParameters) {
387+
if (param.type.kind !== 'config') continue;
388+
389+
for (const prop of param.type.properties) {
390+
391+
const witTypeEither = fromTsType(prop.type, TypeScope.object(param.name, prop.path[-1], prop.type.optional));
392+
if (Either.isLeft(witTypeEither)) return witTypeEither;
393+
394+
let configValueType: ConfigValueType;
395+
if (prop.secret) {
396+
configValueType = { tag: 'shared', val: witTypeEither.val[0] }
397+
} else {
398+
configValueType = { tag: 'local', val: witTypeEither.val[0] }
399+
}
400+
401+
entries.push({
402+
key: prop.path,
403+
value: configValueType,
404+
});
405+
}
406+
}
407+
408+
return Either.right(entries);
409+
}

sdks/ts/packages/golem-ts-sdk/src/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import { AgentTypeRegistry } from './internal/registry/agentTypeRegistry';
2020
import { AgentInitiatorRegistry } from './internal/registry/agentInitiatorRegistry';
2121
import { getRawSelfAgentId } from './host/hostapi';
2222
import { AgentInitiator } from './internal/agentInitiator';
23+
import { TypeInfoInternal } from './internal/typeInfoInternal';
24+
import { loadConfigKey } from './internal/mapping/values/dataValue';
2325

2426
export { BaseAgent } from './baseAgent';
2527
export { AgentId } from './agentId';
@@ -229,3 +231,26 @@ export const saveSnapshot: typeof bindings.saveSnapshot = {
229231
export const loadSnapshot: typeof bindings.loadSnapshot = {
230232
load,
231233
};
234+
235+
export class Secret<T> {
236+
private readonly path: string[];
237+
private readonly typeInfoInternal: TypeInfoInternal;
238+
239+
constructor(path: string[], typeInfoInternal: TypeInfoInternal) {
240+
this.path = path;
241+
this.typeInfoInternal = typeInfoInternal;
242+
}
243+
244+
/** Lazily loads or reloads the secret value */
245+
get(): T {
246+
return loadConfigKey(this.path, this.typeInfoInternal);
247+
}
248+
}
249+
250+
export class Config<T> {
251+
readonly value: T;
252+
253+
constructor(value: T) {
254+
this.value = value;
255+
}
256+
}

sdks/ts/packages/golem-ts-sdk/src/internal/clientGeneration.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,11 @@ function convertToValue(
443443
'Internal error: Value of `Principal` should not be serialized at any point during RPC call',
444444
);
445445

446+
case 'config':
447+
return Either.left(
448+
'Internal error: Value of `Config` should not be serialized at any point during RPC call',
449+
);
450+
446451
case 'multimodal':
447452
const types = typeInfoInternal.types;
448453

@@ -653,6 +658,9 @@ function convertNonMultimodalValueToElementValue(
653658
case 'principal':
654659
throw new Error(`Internal error: Value of 'Principal' should not appear in RPC calls`);
655660

661+
case 'config':
662+
throw new Error(`Internal error: Value of 'Config' should not appear in RPC calls`);
663+
656664
case 'unstructured-binary':
657665
const binaryReference = convertValueToBinaryReference(rpcValueUnwrapped);
658666

sdks/ts/packages/golem-ts-sdk/src/internal/mapping/types/handlers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const handlers: { [K in TsType['kind']]: Handler<K> } = {
6767
others: handleOthers,
6868
'unresolved-type': handleUnresolved,
6969
array: handleArray,
70+
'config': unsupported('Config')
7071
};
7172

7273
function unsupported(kind: string): Handler<any> {

sdks/ts/packages/golem-ts-sdk/src/internal/mapping/values/dataValue.ts

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
import {
16+
isConfig,
1617
isEmptyType,
1718
isOptionalWithQuestionMark,
1819
isPrincipal,
@@ -39,6 +40,9 @@ import * as util from 'node:util';
3940

4041
import * as Value from '../values/Value';
4142
import { getLanguageCodes, getMimeTypes } from '../../schema/helpers';
43+
import { Config, Secret } from '../../..';
44+
import { Type } from '@golemcloud/golem-ts-types-core';
45+
import { getConfigValue } from 'golem:agent/host';
4246

4347
export type ParameterDetail = {
4448
name: string;
@@ -73,7 +77,7 @@ export function deserializeDataValue(
7377
const inputElementsLen = inputElements.length;
7478

7579
// An index that's incremented corresponding to the schema
76-
// The index is incremented for each type unless it is of type `Principal`
80+
// The index is incremented for each type unless it is autoinjected
7781
let schemaBasedIndex = 0;
7882

7983
return Either.all(
@@ -93,6 +97,10 @@ export function deserializeDataValue(
9397
return Either.right(principal);
9498
}
9599

100+
if (isConfig(parameterType)) {
101+
return Either.right(constructConfigType(parameterType));
102+
}
103+
96104
throw new Error(
97105
`Internal error: Not enough elements in data value to deserialize parameter ${parameterDetail.name}`,
98106
);
@@ -107,10 +115,15 @@ export function deserializeDataValue(
107115
);
108116

109117
case 'principal':
110-
// If principal, we do not increment the data value elemnt index,
118+
// If principal, we do not increment the data value element index,
111119
// because principal is not represented in data value
112120
return Either.right(principal);
113121

122+
case 'config':
123+
// If config, we do not increment the data value element index,
124+
// because config is not represented in data value
125+
return Either.right(constructConfigType(parameterType));
126+
114127
case 'unstructured-text':
115128
const unstructuredTextParamName = parameterDetail.name;
116129

@@ -293,6 +306,66 @@ export function deserializeDataValue(
293306
}
294307
}
295308

309+
function constructConfigType(
310+
typeInfoInternal: TypeInfoInternal & { tag: 'config' }
311+
): Config<any> {
312+
// safe as the parent node is config
313+
const properties = (typeInfoInternal.tsType as (Type.Type & { kind: 'config' })).properties;
314+
315+
const root: Record<string, any> = {};
316+
317+
for (const prop of properties) {
318+
const { path } = prop;
319+
if (!path.length) continue;
320+
321+
let current = root;
322+
323+
for (let i = 0; i < path.length - 1; i++) {
324+
const key = path[i];
325+
if (!(key in current)) current[key] = {};
326+
current = current[key];
327+
}
328+
329+
const leafKey = path[path.length - 1];
330+
let leafValue;
331+
if (prop.secret) {
332+
leafValue = new Secret(path, typeInfoInternal)
333+
} else {
334+
leafValue = loadConfigKey(path, typeInfoInternal);
335+
}
336+
337+
current[leafKey] = leafValue;
338+
}
339+
340+
return new Config(root);
341+
}
342+
343+
export function loadConfigKey(
344+
path: string[],
345+
typeInfoInternal: TypeInfoInternal
346+
): any {
347+
const witValue = getConfigValue(path);
348+
349+
const dataValue = createSingleElementTupleDataValue({
350+
tag: 'component-model',
351+
val: witValue,
352+
});
353+
354+
return Either.getOrThrowWith(
355+
deserializeDataValue(
356+
dataValue,
357+
[
358+
{
359+
name: 'config-type',
360+
type: typeInfoInternal
361+
}
362+
],
363+
{ tag: 'anonymous' },
364+
),
365+
(err) => new Error(`Failed to deserialize config: ${err}`),
366+
)
367+
}
368+
296369
// Used to serialize the return type of a method back to DataValue
297370
export function serializeToDataValue(
298371
tsValue: any,
@@ -324,6 +397,11 @@ export function serializeToDataValue(
324397
`Internal Error: Serialization of 'Principal' data should have never happened`,
325398
);
326399

400+
case 'config':
401+
return Either.left(
402+
`Internal Error: Serialization of 'Config' data should have never happened`,
403+
);
404+
327405
case 'unstructured-text':
328406
return Either.right(serializeTextReferenceToDataValue(tsValue));
329407

sdks/ts/packages/golem-ts-sdk/src/internal/schema/constructor.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ const principalHandler: ConstructorParamHandler = {
7474
},
7575
};
7676

77+
const configHandler: ConstructorParamHandler = {
78+
canHandle: (param) => param.type.kind === 'config',
79+
80+
handle: (agentClassName, param, _, collection) => {
81+
AgentConstructorParamRegistry.setType(agentClassName, param.name, {
82+
tag: 'config',
83+
tsType: param.type,
84+
});
85+
86+
collection.addConfigParameter(param.name);
87+
},
88+
};
89+
7790
const unstructuredTextHandler: ConstructorParamHandler = {
7891
canHandle: (param) => param.type.name === 'UnstructuredText',
7992

@@ -155,6 +168,7 @@ const analysedHandler: ConstructorParamHandler = {
155168

156169
const HANDLERS: readonly ConstructorParamHandler[] = [
157170
principalHandler,
171+
configHandler,
158172
unstructuredTextHandler,
159173
unstructuredBinaryHandler,
160174
analysedHandler,

sdks/ts/packages/golem-ts-sdk/src/internal/schema/paramSchema.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ export class ParameterSchemaCollection {
2424
this.parameterSchemas.push({ tag: 'principal', name });
2525
}
2626

27+
addConfigParameter(name: string): void {
28+
this.parameterSchemas.push({ tag: 'config', name });
29+
}
30+
2731
addComponentModelParameter(name: string, schema: ElementSchema): void {
2832
this.parameterSchemas.push({
2933
tag: 'component-model',
@@ -39,18 +43,22 @@ export class ParameterSchemaCollection {
3943

4044
export type ParameterSchema =
4145
| { tag: 'principal'; name: string }
46+
| { tag: 'config'; name: string }
4247
| { tag: 'component-model'; name: string; schema: ElementSchema };
4348

4449
// Remove principal parameters
4550
function getDataSchema(parameterSchemaCollection: ParameterSchema[]): DataSchema {
4651
let nameAndSchema: [string, ElementSchema][] = [];
4752

4853
for (const paramSchema of parameterSchemaCollection) {
49-
if (paramSchema.tag === 'principal') {
50-
}
51-
52-
if (paramSchema.tag === 'component-model') {
53-
nameAndSchema.push([paramSchema.name, paramSchema.schema]);
54+
switch (paramSchema.tag) {
55+
case 'config':
56+
break;
57+
case 'principal':
58+
break;
59+
case 'component-model':
60+
nameAndSchema.push([paramSchema.name, paramSchema.schema]);
61+
break;
5462
}
5563
}
5664
return {

sdks/ts/packages/golem-ts-sdk/src/internal/typeInfoInternal.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export type TypeInfoInternal =
3434
| { tag: 'unstructured-text'; val: TextDescriptor; tsType: Type.Type }
3535
| { tag: 'unstructured-binary'; val: BinaryDescriptor; tsType: Type.Type }
3636
| { tag: 'principal'; tsType: Type.Type }
37+
| { tag: 'config'; tsType: Type.Type }
3738
| {
3839
tag: 'multimodal';
3940
types: ParameterDetail[];
@@ -54,6 +55,10 @@ export function isPrincipal(typeInfoInternal: TypeInfoInternal): boolean {
5455
return typeInfoInternal.tag === 'principal';
5556
}
5657

58+
export function isConfig(typeInfoInternal: TypeInfoInternal): typeInfoInternal is (TypeInfoInternal & { tag: 'config'}) {
59+
return typeInfoInternal.tag === 'config';
60+
}
61+
5762
export function isEmptyType(typeInfoInternal: TypeInfoInternal): boolean {
5863
if (typeInfoInternal.tag === 'analysed') {
5964
const analysed = typeInfoInternal.val;
@@ -87,6 +92,8 @@ export function convertTypeInfoToElementSchema(
8792
});
8893
case 'principal':
8994
return Either.left('Cannot convert `Principal` type information to ElementSchema');
95+
case 'config':
96+
return Either.left('Cannot convert `Principal` type information to ElementSchema');
9097
case 'multimodal':
9198
return Either.left('Cannot convert multimodal type information to ElementSchema');
9299
}
@@ -110,6 +117,8 @@ export function getMultimodalDataSchemaFromTypeInternal(
110117
return Either.left('cannot get multimodal DataSchema from unstructured-binary type info');
111118
case 'principal':
112119
return Either.left('cannot get multimodal DataSchema from principal type info');
120+
case 'config':
121+
return Either.left('cannot get multimodal DataSchema from config type info');
113122
case 'multimodal':
114123
const parameterDetails = typeInfoInternal.types;
115124

@@ -191,6 +200,8 @@ export function getReturnTypeDataSchemaFromTypeInternal(
191200
});
192201
case 'principal':
193202
return Either.left('Principal cannot be used as a method return type');
203+
case 'config':
204+
return Either.left('Config cannot be used as a method return type');
194205
case 'multimodal':
195206
return getMultimodalDataSchemaFromTypeInternal(typeInfoInternal);
196207
}

0 commit comments

Comments
 (0)