@artemiskit/core 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +12 -0
- package/dist/adapters/types.d.ts +7 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/agent-workflow/catalog.d.ts +2 -1
- package/dist/agent-workflow/catalog.d.ts.map +1 -1
- package/dist/agent-workflow/environment.d.ts +47 -0
- package/dist/agent-workflow/environment.d.ts.map +1 -0
- package/dist/agent-workflow/index.d.ts +4 -1
- package/dist/agent-workflow/index.d.ts.map +1 -1
- package/dist/agent-workflow/parser.d.ts +1 -1
- package/dist/agent-workflow/parser.d.ts.map +1 -1
- package/dist/agent-workflow/sandbox-fixtures/qualify.d.ts +2 -0
- package/dist/agent-workflow/sandbox-fixtures/qualify.d.ts.map +1 -0
- package/dist/agent-workflow/sandbox.d.ts +12 -0
- package/dist/agent-workflow/sandbox.d.ts.map +1 -0
- package/dist/agent-workflow/schema.d.ts +117 -7
- package/dist/agent-workflow/schema.d.ts.map +1 -1
- package/dist/agent-workflow/session.d.ts +113 -0
- package/dist/agent-workflow/session.d.ts.map +1 -0
- package/dist/agent-workflow/target.d.ts +20 -7
- package/dist/agent-workflow/target.d.ts.map +1 -1
- package/dist/index.js +1366 -21
- package/package.json +1 -1
- package/src/adapters/types.ts +7 -0
- package/src/agent-workflow/catalog.ts +4 -3
- package/src/agent-workflow/environment.ts +207 -0
- package/src/agent-workflow/index.ts +5 -1
- package/src/agent-workflow/parser.ts +1 -1
- package/src/agent-workflow/sandbox-fixtures/qualify.ts +305 -0
- package/src/agent-workflow/sandbox.test.ts +117 -0
- package/src/agent-workflow/sandbox.ts +438 -0
- package/src/agent-workflow/schema.ts +18 -2
- package/src/agent-workflow/session.test.ts +629 -0
- package/src/agent-workflow/session.ts +1119 -0
- package/src/agent-workflow/target.test.ts +5 -1
- package/src/agent-workflow/target.ts +82 -17
|
@@ -71,6 +71,7 @@ describe('ModelClient agent target', () => {
|
|
|
71
71
|
);
|
|
72
72
|
const result = await target.turn(request);
|
|
73
73
|
expect(received).toEqual({
|
|
74
|
+
maxRetries: 0,
|
|
74
75
|
prompt: request.messages,
|
|
75
76
|
tools: request.tools,
|
|
76
77
|
model: request.model,
|
|
@@ -167,7 +168,10 @@ describe('ModelClient agent target', () => {
|
|
|
167
168
|
const target = createModelClientTarget(
|
|
168
169
|
client({ generate: async () => ({ ...response, ...override }) })
|
|
169
170
|
);
|
|
170
|
-
expect(await target.turn(request)).
|
|
171
|
+
expect(await target.turn(request)).toMatchObject({
|
|
172
|
+
status: 'invalid',
|
|
173
|
+
code: 'invalid_response',
|
|
174
|
+
});
|
|
171
175
|
});
|
|
172
176
|
test('rejects tool calls beyond the declared per-turn budget', async () => {
|
|
173
177
|
expect(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import Ajv from 'ajv';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import type { GenerateOptions, ModelClient, TokenUsage, ToolCall } from '../adapters/types';
|
|
5
|
+
import { getWorkflowTool } from './catalog';
|
|
4
6
|
|
|
5
7
|
const identifier = z.string().min(1).max(256);
|
|
6
8
|
const toolName = z.string().regex(/^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/);
|
|
@@ -21,7 +23,7 @@ const messageSchema = z
|
|
|
21
23
|
})
|
|
22
24
|
.strict();
|
|
23
25
|
const timeoutSchema = z.number().int().min(1).max(2_147_483_647);
|
|
24
|
-
const
|
|
26
|
+
export const agentTurnRequestSchema = z
|
|
25
27
|
.object({
|
|
26
28
|
messages: z.array(messageSchema).min(1).max(1000),
|
|
27
29
|
tools: z
|
|
@@ -59,6 +61,7 @@ const requestSchema = z
|
|
|
59
61
|
})
|
|
60
62
|
.strict();
|
|
61
63
|
const resultSchema = z.object({
|
|
64
|
+
usageAvailable: z.boolean().optional(),
|
|
62
65
|
id: identifier,
|
|
63
66
|
model: identifier,
|
|
64
67
|
text,
|
|
@@ -75,8 +78,15 @@ const resultSchema = z.object({
|
|
|
75
78
|
functionCall: z.unknown().optional(),
|
|
76
79
|
});
|
|
77
80
|
|
|
78
|
-
export type AgentTurnRequest = z.infer<typeof
|
|
81
|
+
export type AgentTurnRequest = z.infer<typeof agentTurnRequestSchema>;
|
|
79
82
|
export type AgentTargetFailure = {
|
|
83
|
+
rejectedCall?: {
|
|
84
|
+
requestedCallIdHash: string;
|
|
85
|
+
tool: string;
|
|
86
|
+
reason: 'undeclared_tool' | 'invalid_arguments' | 'duplicate_id';
|
|
87
|
+
};
|
|
88
|
+
tokens?: TokenUsage;
|
|
89
|
+
usageAvailable?: boolean;
|
|
80
90
|
status: 'unsupported' | 'invalid' | 'error';
|
|
81
91
|
code:
|
|
82
92
|
| 'invalid_request'
|
|
@@ -89,8 +99,7 @@ export type AgentTargetFailure = {
|
|
|
89
99
|
export type AgentTargetCapabilities = {
|
|
90
100
|
status: 'available';
|
|
91
101
|
toolUse: boolean;
|
|
92
|
-
|
|
93
|
-
transportCancellation: false;
|
|
102
|
+
transportCancellation: boolean;
|
|
94
103
|
};
|
|
95
104
|
export type AgentTurnResult =
|
|
96
105
|
| AgentTargetFailure
|
|
@@ -101,6 +110,7 @@ export type AgentTurnResult =
|
|
|
101
110
|
message: { role: 'assistant'; content: string; tool_calls?: ToolCall[] };
|
|
102
111
|
/** Adapter-reported counts only; zero can mean unavailable in existing adapters. */
|
|
103
112
|
tokens: TokenUsage;
|
|
113
|
+
usageAvailable?: boolean;
|
|
104
114
|
latencyMs: number;
|
|
105
115
|
finishReason?: 'stop' | 'length' | 'tool_calls' | 'content_filter';
|
|
106
116
|
};
|
|
@@ -113,6 +123,8 @@ export interface AgentTarget {
|
|
|
113
123
|
signal?: AbortSignal
|
|
114
124
|
): Promise<AgentTargetCapabilities | AgentTargetFailure>;
|
|
115
125
|
turn(request: AgentTurnRequest, signal?: AbortSignal): Promise<AgentTurnResult>;
|
|
126
|
+
/** Wait for underlying callbacks hidden behind a bounded turn facade. */
|
|
127
|
+
drain?(options: { timeoutMs: number }): Promise<{ pendingOperations: number }>;
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
const failure = (
|
|
@@ -145,7 +157,7 @@ function bounded<T>(
|
|
|
145
157
|
});
|
|
146
158
|
}
|
|
147
159
|
|
|
148
|
-
function
|
|
160
|
+
export function validWorkflowTranscript(messages: AgentTurnRequest['messages']): boolean {
|
|
149
161
|
const seen = new Set<string>();
|
|
150
162
|
const pending = new Set<string>();
|
|
151
163
|
for (const message of messages) {
|
|
@@ -172,8 +184,8 @@ function validTranscript(messages: AgentTurnRequest['messages']): boolean {
|
|
|
172
184
|
}
|
|
173
185
|
|
|
174
186
|
/**
|
|
175
|
-
* Bridge existing adapters without provider-specific dispatch.
|
|
176
|
-
* transport
|
|
187
|
+
* Bridge existing adapters without provider-specific dispatch. Workflow calls disable supported
|
|
188
|
+
* transport retries and propagate abort only when the adapter declares transport cancellation.
|
|
177
189
|
* Returned text/tool arguments are working conversation data, not sanitized retained evidence.
|
|
178
190
|
*/
|
|
179
191
|
export function createModelClientTarget(client: ModelClient): AgentTarget {
|
|
@@ -185,26 +197,44 @@ export function createModelClientTarget(client: ModelClient): AgentTarget {
|
|
|
185
197
|
) {
|
|
186
198
|
throw new TypeError('Invalid ModelClient');
|
|
187
199
|
}
|
|
200
|
+
const pending = new Set<Promise<unknown>>();
|
|
201
|
+
const track = <T>(promise: Promise<T>): Promise<T> => {
|
|
202
|
+
pending.add(promise);
|
|
203
|
+
void promise.then(
|
|
204
|
+
() => pending.delete(promise),
|
|
205
|
+
() => pending.delete(promise)
|
|
206
|
+
);
|
|
207
|
+
return promise;
|
|
208
|
+
};
|
|
188
209
|
const readCapabilities = async (): Promise<AgentTargetCapabilities | AgentTargetFailure> => {
|
|
189
|
-
const value = await client.capabilities();
|
|
210
|
+
const value = await track(client.capabilities());
|
|
190
211
|
if (!value || typeof value.toolUse !== 'boolean') return failure('invalid', 'invalid_response');
|
|
191
|
-
return {
|
|
212
|
+
return {
|
|
213
|
+
status: 'available',
|
|
214
|
+
toolUse: value.toolUse,
|
|
215
|
+
transportCancellation: value.transportCancellation === true,
|
|
216
|
+
};
|
|
192
217
|
};
|
|
193
218
|
return {
|
|
194
219
|
provider: client.provider,
|
|
220
|
+
async drain({ timeoutMs }) {
|
|
221
|
+
if (!timeoutSchema.safeParse(timeoutMs).success) return { pendingOperations: pending.size };
|
|
222
|
+
await bounded(() => Promise.allSettled([...pending]), timeoutMs);
|
|
223
|
+
return { pendingOperations: pending.size };
|
|
224
|
+
},
|
|
195
225
|
async capabilities(options, signal) {
|
|
196
226
|
if (!timeoutSchema.safeParse(options?.timeoutMs).success)
|
|
197
227
|
return failure('invalid', 'invalid_request');
|
|
198
228
|
return bounded(readCapabilities, options.timeoutMs, signal);
|
|
199
229
|
},
|
|
200
230
|
async turn(request, signal) {
|
|
201
|
-
let parsed: ReturnType<typeof
|
|
231
|
+
let parsed: ReturnType<typeof agentTurnRequestSchema.safeParse>;
|
|
202
232
|
try {
|
|
203
|
-
parsed =
|
|
233
|
+
parsed = agentTurnRequestSchema.safeParse(request);
|
|
204
234
|
} catch {
|
|
205
235
|
return failure('invalid', 'invalid_request');
|
|
206
236
|
}
|
|
207
|
-
if (!parsed.success || !
|
|
237
|
+
if (!parsed.success || !validWorkflowTranscript(parsed.data.messages))
|
|
208
238
|
return failure('invalid', 'invalid_request');
|
|
209
239
|
const input = parsed.data;
|
|
210
240
|
const validators = new Map<string, ReturnType<Ajv['compile']>>();
|
|
@@ -223,6 +253,11 @@ export function createModelClientTarget(client: ModelClient): AgentTarget {
|
|
|
223
253
|
return failure('invalid', 'invalid_request');
|
|
224
254
|
}
|
|
225
255
|
const started = Date.now();
|
|
256
|
+
const controller = new AbortController();
|
|
257
|
+
const abort = () => controller.abort();
|
|
258
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
259
|
+
if (signal?.aborted) controller.abort();
|
|
260
|
+
const timer = setTimeout(abort, input.budgets.timeoutMs);
|
|
226
261
|
return bounded(
|
|
227
262
|
async (): Promise<AgentTurnResult> => {
|
|
228
263
|
const capabilities = await readCapabilities();
|
|
@@ -232,12 +267,14 @@ export function createModelClientTarget(client: ModelClient): AgentTarget {
|
|
|
232
267
|
if (signal?.aborted) return failure('error', 'aborted');
|
|
233
268
|
if (Date.now() - started >= input.budgets.timeoutMs) return failure('error', 'timeout');
|
|
234
269
|
const options: GenerateOptions = {
|
|
270
|
+
maxRetries: 0,
|
|
235
271
|
prompt: input.messages,
|
|
236
272
|
tools: input.tools,
|
|
237
273
|
model: input.model,
|
|
238
274
|
...input.generation,
|
|
275
|
+
...(capabilities.transportCancellation ? { signal: controller.signal } : {}),
|
|
239
276
|
};
|
|
240
|
-
const generated = resultSchema.safeParse(await client.generate(options));
|
|
277
|
+
const generated = resultSchema.safeParse(await track(client.generate(options)));
|
|
241
278
|
if (!generated.success) return failure('invalid', 'invalid_response');
|
|
242
279
|
const result = generated.data;
|
|
243
280
|
const calls = result.toolCalls ?? [];
|
|
@@ -252,10 +289,26 @@ export function createModelClientTarget(client: ModelClient): AgentTarget {
|
|
|
252
289
|
const ids = new Set(
|
|
253
290
|
input.messages.flatMap((message) => message.tool_calls?.map((call) => call.id) ?? [])
|
|
254
291
|
);
|
|
292
|
+
const rejected = (
|
|
293
|
+
call: ToolCall,
|
|
294
|
+
reason: NonNullable<AgentTargetFailure['rejectedCall']>['reason']
|
|
295
|
+
): AgentTargetFailure => ({
|
|
296
|
+
...failure('invalid', 'invalid_response'),
|
|
297
|
+
tokens: result.tokens,
|
|
298
|
+
...(result.usageAvailable !== undefined
|
|
299
|
+
? { usageAvailable: result.usageAvailable }
|
|
300
|
+
: {}),
|
|
301
|
+
rejectedCall: {
|
|
302
|
+
requestedCallIdHash: createHash('sha256').update(call.id).digest('hex'),
|
|
303
|
+
tool: getWorkflowTool(call.function.name)?.id ?? 'unknown',
|
|
304
|
+
reason,
|
|
305
|
+
},
|
|
306
|
+
});
|
|
255
307
|
for (const call of calls) {
|
|
256
|
-
if (ids.has(call.id)) return
|
|
308
|
+
if (ids.has(call.id)) return rejected(call, 'duplicate_id');
|
|
257
309
|
ids.add(call.id);
|
|
258
310
|
const validate = validators.get(call.function.name);
|
|
311
|
+
if (!validate) return rejected(call, 'undeclared_tool');
|
|
259
312
|
try {
|
|
260
313
|
const args: unknown = JSON.parse(call.function.arguments);
|
|
261
314
|
if (
|
|
@@ -265,9 +318,9 @@ export function createModelClientTarget(client: ModelClient): AgentTarget {
|
|
|
265
318
|
!validate ||
|
|
266
319
|
validate(args) !== true
|
|
267
320
|
)
|
|
268
|
-
return
|
|
321
|
+
return rejected(call, 'invalid_arguments');
|
|
269
322
|
} catch {
|
|
270
|
-
return
|
|
323
|
+
return rejected(call, 'invalid_arguments');
|
|
271
324
|
}
|
|
272
325
|
}
|
|
273
326
|
return {
|
|
@@ -280,13 +333,25 @@ export function createModelClientTarget(client: ModelClient): AgentTarget {
|
|
|
280
333
|
...(calls.length ? { tool_calls: calls } : {}),
|
|
281
334
|
},
|
|
282
335
|
tokens: result.tokens,
|
|
336
|
+
...(result.usageAvailable !== undefined
|
|
337
|
+
? { usageAvailable: result.usageAvailable }
|
|
338
|
+
: {}),
|
|
283
339
|
latencyMs: result.latencyMs,
|
|
284
340
|
finishReason: result.finishReason,
|
|
285
341
|
};
|
|
286
342
|
},
|
|
287
343
|
input.budgets.timeoutMs,
|
|
288
344
|
signal
|
|
289
|
-
)
|
|
345
|
+
)
|
|
346
|
+
.then((result) =>
|
|
347
|
+
controller.signal.aborted
|
|
348
|
+
? failure('error', signal?.aborted ? 'aborted' : 'timeout')
|
|
349
|
+
: result
|
|
350
|
+
)
|
|
351
|
+
.finally(() => {
|
|
352
|
+
clearTimeout(timer);
|
|
353
|
+
signal?.removeEventListener('abort', abort);
|
|
354
|
+
});
|
|
290
355
|
},
|
|
291
356
|
};
|
|
292
357
|
}
|