@statelyai/agent 2.0.0-alpha.28 → 2.0.0-alpha.30
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/dist/ai-sdk.cjs +150 -27
- package/dist/ai-sdk.d.cts +59 -36
- package/dist/ai-sdk.d.mts +59 -36
- package/dist/ai-sdk.mjs +148 -24
- package/dist/{decision-RQ0iv0oZ.mjs → decision-BHkvuImD.mjs} +74 -8
- package/dist/{decision-CmM8-HHz.cjs → decision-CIb7ddIv.cjs} +113 -30
- package/dist/{text-logic-f5F5sCdc.cjs → event-log-BZRfBRd7.cjs} +814 -7
- package/dist/{text-logic-CIqHAmBi.mjs → event-log-DJCMHek_.mjs} +680 -4
- package/dist/{event-log-store-BIPOz-di.d.mts → event-log-store-CIIJohjj.d.mts} +90 -5
- package/dist/{event-log-store-2xzFmgr2.d.cts → event-log-store-CLAC9q2Y.d.cts} +90 -5
- package/dist/event-log-store-DZR4IIWe.cjs +95 -0
- package/dist/event-log-store-DsP6GCRh.mjs +84 -0
- package/dist/index.cjs +576 -58
- package/dist/index.d.cts +104 -510
- package/dist/index.d.mts +104 -510
- package/dist/index.mjs +547 -32
- package/dist/log.cjs +77 -20
- package/dist/log.d.cts +5 -5
- package/dist/log.d.mts +5 -5
- package/dist/log.mjs +58 -3
- package/dist/machines.cjs +1 -1
- package/dist/machines.d.cts +15 -15
- package/dist/machines.d.mts +15 -15
- package/dist/machines.mjs +1 -1
- package/dist/model-source-BCKg4qJ1.cjs +44 -0
- package/dist/model-source-BaHwJ1wK.mjs +39 -0
- package/dist/openai.cjs +11 -11
- package/dist/openai.d.cts +28 -26
- package/dist/openai.d.mts +28 -26
- package/dist/openai.mjs +3 -3
- package/dist/otel.cjs +5 -4
- package/dist/otel.d.cts +5 -7
- package/dist/otel.d.mts +5 -7
- package/dist/otel.mjs +5 -3
- package/dist/{run-agent-xNTxpt82.mjs → run-agent-BpHqkaZw.mjs} +181 -44
- package/dist/{run-agent-COXXc0RW.d.mts → run-agent-CAVt5R-r.d.mts} +95 -30
- package/dist/{run-agent-ByO-Bxyd.d.cts → run-agent-CTlMc3Jb.d.cts} +95 -30
- package/dist/{run-agent-Dmf8GV2d.cjs → run-agent-ctLCvNgL.cjs} +230 -88
- package/dist/{setup-agent-BuanDXeh.mjs → setup-agent-7rV_cujV.mjs} +15 -9
- package/dist/setup-agent-CGELn1qZ.d.mts +577 -0
- package/dist/setup-agent-CLPCkXsu.d.cts +577 -0
- package/dist/{setup-agent-DV4cTou-.cjs → setup-agent-DoUk7EdC.cjs} +31 -20
- package/dist/tanstack-ai.cjs +627 -0
- package/dist/tanstack-ai.d.cts +204 -0
- package/dist/tanstack-ai.d.mts +204 -0
- package/dist/tanstack-ai.mjs +618 -0
- package/dist/testing.cjs +204 -985
- package/dist/testing.d.cts +132 -347
- package/dist/testing.d.mts +132 -347
- package/dist/testing.mjs +191 -973
- package/dist/{text-logic-IjfYy9DP.d.cts → text-logic-BRYOPWvY.d.mts} +52 -12
- package/dist/{text-logic-D-czVPYp.d.mts → text-logic-DmciuVUQ.d.cts} +52 -12
- package/dist/{types-BgNbNKnJ.d.mts → types-Dp5_Ru4R.d.cts} +2 -2
- package/dist/{types-hR5f5YXa.d.cts → types-Dp5_Ru4R.d.mts} +2 -2
- package/dist/usage-BkM3z02Y.d.mts +17 -0
- package/dist/usage-Cm_ovz9V.d.cts +17 -0
- package/dist/{utils-_q7F-AZu.d.mts → utils-BnXdWih3.d.mts} +1 -2
- package/dist/{utils-o4YRdHJX.d.cts → utils-ebVpCYoF.d.cts} +1 -2
- package/dist/validate.cjs +239 -8
- package/dist/validate.d.cts +110 -3
- package/dist/validate.d.mts +110 -3
- package/dist/validate.mjs +238 -7
- package/dist/verify-COFWyprJ.mjs +1181 -0
- package/dist/verify-CVlsTZ-V.cjs +1222 -0
- package/dist/verify-Cn86rT8l.d.mts +414 -0
- package/dist/verify-Dzwd4Mvk.d.cts +414 -0
- package/package.json +44 -31
- package/readme.md +40 -28
- package/skills/add-evals/SKILL.md +129 -0
- package/skills/add-traces/SKILL.md +135 -0
- package/skills/generate-machine/SKILL.md +143 -69
- package/skills/graph-engineering/SKILL.md +33 -0
- package/skills/improve-machine/SKILL.md +241 -0
- package/skills/improve-machine/reference.md +101 -0
- package/skills/loop-to-machine/SKILL.md +183 -0
- package/skills/loop-to-machine/policy.md +158 -0
- package/dist/event-log-store-2pAFiWuc.mjs +0 -576
- package/dist/event-log-store-C-COhIXP.cjs +0 -702
- package/dist/usage-BdEjFuIi.mjs +0 -5
- package/dist/usage-efOQPdf_.cjs +0 -10
package/dist/ai-sdk.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { R as providerOutputSchema, ct as AgentTruncatedError, j as getAgentOutputMode, tt as isStandardSchema } from "./event-log-DJCMHek_.mjs";
|
|
2
|
+
import { c as renderDecisionAttempts, m as isRecord, o as decisionOptionIds } from "./decision-BHkvuImD.mjs";
|
|
3
|
+
import { t as resolveModelSource } from "./model-source-BaHwJ1wK.mjs";
|
|
4
|
+
import { InvalidResponseDataError, NoObjectGeneratedError, Output, experimental_decide, generateText, jsonSchema, stepCountIs, streamText, tool } from "ai";
|
|
4
5
|
//#region src/ai-sdk/mappers.ts
|
|
5
6
|
/**
|
|
6
7
|
* Maps an {@link AgentTools} map onto AI SDK `tool()` definitions. A tool that
|
|
@@ -23,7 +24,7 @@ function toAiSdkTools(tools) {
|
|
|
23
24
|
})]);
|
|
24
25
|
continue;
|
|
25
26
|
}
|
|
26
|
-
if (isStandardSchema(descriptor.inputSchema)) {
|
|
27
|
+
if (isStandardSchema(descriptor.inputSchema) || descriptor.type === "provider") {
|
|
27
28
|
entries.push([name, descriptor]);
|
|
28
29
|
continue;
|
|
29
30
|
}
|
|
@@ -173,6 +174,53 @@ function toAiSdkEventTools(events) {
|
|
|
173
174
|
inputSchema: event.inputSchema ? event.inputSchema : unknownSchema
|
|
174
175
|
})]));
|
|
175
176
|
}
|
|
177
|
+
/** Tool name of the single closed-choice tool {@link toAiSdkOptionTool} builds. */
|
|
178
|
+
const OPTION_TOOL_NAME = "choose_option";
|
|
179
|
+
/**
|
|
180
|
+
* A closed choice as ONE AI SDK tool: its input is `{ option }`, an enum of
|
|
181
|
+
* option ids (see core's `decisionOptionIds`), with each id's description in
|
|
182
|
+
* the tool description. Map the answer back with `ids.indexOf(input.option)`.
|
|
183
|
+
*/
|
|
184
|
+
function toAiSdkOptionTool(options) {
|
|
185
|
+
const ids = decisionOptionIds(options);
|
|
186
|
+
const lines = options.map((option, index) => option.description ? `- ${ids[index]}: ${option.description}` : `- ${ids[index]}`);
|
|
187
|
+
return {
|
|
188
|
+
ids,
|
|
189
|
+
tools: { [OPTION_TOOL_NAME]: tool({
|
|
190
|
+
description: `Choose exactly one option:\n${lines.join("\n")}`,
|
|
191
|
+
inputSchema: jsonSchema({
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: { option: {
|
|
194
|
+
type: "string",
|
|
195
|
+
enum: ids,
|
|
196
|
+
description: "The id of the chosen option."
|
|
197
|
+
} },
|
|
198
|
+
required: ["option"],
|
|
199
|
+
additionalProperties: false
|
|
200
|
+
})
|
|
201
|
+
}) }
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The event a closed-choice tool call names: the offered option whose id is
|
|
206
|
+
* `input.option`. A call to another tool, or an id that is not offered, maps
|
|
207
|
+
* to {@link unofferedOption}, so the choice is retried instead of thrown.
|
|
208
|
+
*/
|
|
209
|
+
function fromAiSdkOptionCall(toolCall, options, ids) {
|
|
210
|
+
if (toolCall.toolName !== OPTION_TOOL_NAME) return unofferedOption(`tool '${toolCall.toolName}'`, ids);
|
|
211
|
+
const id = isRecord(toolCall.input) ? toolCall.input.option : void 0;
|
|
212
|
+
const option = typeof id === "string" ? options[ids.indexOf(id)] : void 0;
|
|
213
|
+
return option ? option.event : unofferedOption(`option ${JSON.stringify(id)}`, ids);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* A stand-in event for a closed-choice answer that names no offered option.
|
|
217
|
+
* Its `type` is no candidate event, so `resolveDecision` records it as a
|
|
218
|
+
* failed `'unknown-event'` attempt (the type, which names the answer and the
|
|
219
|
+
* valid ids, is the attempt's reason) and asks again.
|
|
220
|
+
*/
|
|
221
|
+
function unofferedOption(answer, ids) {
|
|
222
|
+
return { type: `${answer} was not offered (valid ids: ${ids.join(", ") || "none"})` };
|
|
223
|
+
}
|
|
176
224
|
/**
|
|
177
225
|
* Messages for a decision request, with prior failed `attempts` (§2.6)
|
|
178
226
|
* rendered as appended user messages (via core's {@link renderDecisionAttempts})
|
|
@@ -196,9 +244,12 @@ function maxStepsSetting(request) {
|
|
|
196
244
|
const maxSteps = request.maxSteps;
|
|
197
245
|
return typeof maxSteps === "number" ? { stopWhen: stepCountIs(maxSteps) } : {};
|
|
198
246
|
}
|
|
247
|
+
function isDecisionModel(model) {
|
|
248
|
+
return isRecord(model) && (typeof model.doDecide === "function" || typeof model.doEvaluate === "function");
|
|
249
|
+
}
|
|
199
250
|
/**
|
|
200
251
|
* Splits a portable `"provider/model-id"` model ref (the convention JSON
|
|
201
|
-
* workflows and registry-less hosts use, e.g. `"openai/gpt-
|
|
252
|
+
* workflows and registry-less hosts use, e.g. `"openai/gpt-6-luna"`) into
|
|
202
253
|
* its parts. A ref with no `/` has no provider — `modelId` is the whole ref.
|
|
203
254
|
* The standard building block for a host's `resolveModel`:
|
|
204
255
|
*
|
|
@@ -230,12 +281,22 @@ function resolveAiSdkModel(options, modelRef) {
|
|
|
230
281
|
if (!entry) throw new Error(`createAiSdkExecutors: unknown model '${modelRef}'.`);
|
|
231
282
|
return isModelWithSettings(entry) ? entry : { model: entry };
|
|
232
283
|
}
|
|
284
|
+
function resolveLanguageModel(options, request) {
|
|
285
|
+
const { model, settings } = resolveAiSdkModel(options, request.model);
|
|
286
|
+
if (isDecisionModel(model)) throw new Error(`createAiSdkExecutors: model '${request.model}' is a decision model, which only answers decide requests with options; text request '${request.name ?? "(unnamed)"}' needs a language model.`);
|
|
287
|
+
return {
|
|
288
|
+
model,
|
|
289
|
+
...settings ? { settings } : {}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
233
292
|
/**
|
|
234
293
|
* The `LanguageModel` inside a map entry, whichever form it takes. `AiSdkModelMap`
|
|
235
294
|
* widens its return to {@link AiSdkModelMap}, so reading an entry out of the map
|
|
236
|
-
* to hand to a raw AI SDK call needs this to get past the union.
|
|
295
|
+
* to hand to a raw AI SDK call needs this to get past the union. Throws for a
|
|
296
|
+
* decision model.
|
|
237
297
|
*/
|
|
238
298
|
function toLanguageModel(entry) {
|
|
299
|
+
if (isDecisionModel(entry)) throw new TypeError("toLanguageModel: the entry is a decision model, not a language model.");
|
|
239
300
|
return isModelWithSettings(entry) ? entry.model : entry;
|
|
240
301
|
}
|
|
241
302
|
function isModelWithSettings(entry) {
|
|
@@ -266,23 +327,10 @@ function truncated(request, info, partialOutput, cause) {
|
|
|
266
327
|
...cause !== void 0 ? { cause } : {}
|
|
267
328
|
});
|
|
268
329
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
* decide }` executor set consumed by `createAgentRuntime`/`executeAgentRequest`. `ai`
|
|
272
|
-
* must not become a dependency of core `src/` files — this subpath is the one
|
|
273
|
-
* place it's imported, and callers must supply their own model resolver so no
|
|
274
|
-
* concrete provider package (e.g. `@ai-sdk/openai`) becomes a dependency here
|
|
275
|
-
* either.
|
|
276
|
-
*
|
|
277
|
-
* @example
|
|
278
|
-
* ```ts
|
|
279
|
-
* const executors = createAiSdkExecutors({ models: { quick: openai('gpt-5.4-mini') } });
|
|
280
|
-
* const result = await runToQuiescence(createAgentRuntime(machine, { executors }), { input });
|
|
281
|
-
* ```
|
|
282
|
-
*/
|
|
283
|
-
function createAiSdkExecutors(options) {
|
|
330
|
+
function createAiSdkExecutors(source) {
|
|
331
|
+
const options = resolveModelSource("createAiSdkExecutors", source);
|
|
284
332
|
const generateText$1 = async (request, info) => {
|
|
285
|
-
const { model, settings: modelSettings } =
|
|
333
|
+
const { model, settings: modelSettings } = resolveLanguageModel(options, request);
|
|
286
334
|
const common = {
|
|
287
335
|
model,
|
|
288
336
|
abortSignal: info?.signal,
|
|
@@ -331,7 +379,7 @@ function createAiSdkExecutors(options) {
|
|
|
331
379
|
};
|
|
332
380
|
};
|
|
333
381
|
const streamText$1 = async (request, info) => {
|
|
334
|
-
const { model, settings: modelSettings } =
|
|
382
|
+
const { model, settings: modelSettings } = resolveLanguageModel(options, request);
|
|
335
383
|
const result = streamText({
|
|
336
384
|
model,
|
|
337
385
|
abortSignal: info?.signal,
|
|
@@ -351,7 +399,9 @@ function createAiSdkExecutors(options) {
|
|
|
351
399
|
};
|
|
352
400
|
const decide = async (request, info) => {
|
|
353
401
|
const { model, settings: modelSettings } = resolveAiSdkModel(options, request.model);
|
|
354
|
-
|
|
402
|
+
if (isDecisionModel(model)) return decideWithDecisionModel(model, request, info);
|
|
403
|
+
const choice = request.options ? toAiSdkOptionTool(request.options) : void 0;
|
|
404
|
+
const tools = choice ? choice.tools : toAiSdkEventTools(request.events);
|
|
355
405
|
const messages = toDecisionMessages(request);
|
|
356
406
|
const result = await generateText({
|
|
357
407
|
model,
|
|
@@ -370,6 +420,11 @@ function createAiSdkExecutors(options) {
|
|
|
370
420
|
});
|
|
371
421
|
const toolCall = result.toolCalls[0];
|
|
372
422
|
if (!toolCall) throw new Error("createAiSdkExecutors: decide — model did not call an event tool.");
|
|
423
|
+
if (choice) return {
|
|
424
|
+
event: fromAiSdkOptionCall(toolCall, request.options, choice.ids),
|
|
425
|
+
usage: toAgentCallUsage(result.usage),
|
|
426
|
+
finishReason: result.finishReason
|
|
427
|
+
};
|
|
373
428
|
const chosenEvent = request.events.find((event) => event.toolName === toolCall.toolName);
|
|
374
429
|
if (!chosenEvent) throw new Error(`createAiSdkExecutors: decide — model called unknown tool '${toolCall.toolName}'.`);
|
|
375
430
|
return {
|
|
@@ -387,5 +442,74 @@ function createAiSdkExecutors(options) {
|
|
|
387
442
|
decide
|
|
388
443
|
};
|
|
389
444
|
}
|
|
445
|
+
/** The question id a decision model's closed choice is asked under. */
|
|
446
|
+
const DECISION_QUESTION_ID = "next";
|
|
447
|
+
async function decideWithDecisionModel(model, request, info) {
|
|
448
|
+
const offered = request.options;
|
|
449
|
+
if (!offered?.length) throw new Error(`createAiSdkExecutors: decision '${request.name ?? "(unnamed)"}' uses decision model '${request.model}', which needs a closed choice: pass \`payloads\` on agent.decide, or use decideNext.`);
|
|
450
|
+
const ids = decisionOptionIds(offered);
|
|
451
|
+
const criteria = Object.fromEntries(offered.map((option, index) => [ids[index], option.description ?? JSON.stringify(option.event)]));
|
|
452
|
+
const instructions = [[request.system, request.prompt].filter(Boolean).join("\n\n") || "Which option should happen next?", ...renderDecisionAttempts(request).map((message) => message.content)].join("\n\n");
|
|
453
|
+
const ask = () => experimental_decide({
|
|
454
|
+
model,
|
|
455
|
+
state: toDecisionState(request.input),
|
|
456
|
+
questions: { [DECISION_QUESTION_ID]: {
|
|
457
|
+
type: "choice",
|
|
458
|
+
instructions,
|
|
459
|
+
criteria
|
|
460
|
+
} },
|
|
461
|
+
...info?.signal ? { abortSignal: info.signal } : {}
|
|
462
|
+
});
|
|
463
|
+
let result;
|
|
464
|
+
try {
|
|
465
|
+
result = await ask();
|
|
466
|
+
} catch (error) {
|
|
467
|
+
const chosen = unofferedChoice(error, ids);
|
|
468
|
+
if (chosen === void 0) throw error;
|
|
469
|
+
return decisionResult(unofferedOption(`option ${JSON.stringify(chosen)}`, ids));
|
|
470
|
+
}
|
|
471
|
+
const answer = result.answers[DECISION_QUESTION_ID];
|
|
472
|
+
const option = offered[ids.indexOf(answer.choice)];
|
|
473
|
+
if (!option) return decisionResult(unofferedOption(`option ${JSON.stringify(answer.choice)}`, ids));
|
|
474
|
+
const reported = (result.providerMetadata?.typesafe?.confidence)?.[DECISION_QUESTION_ID];
|
|
475
|
+
const confidence = typeof reported === "number" ? reported : answer.probabilities?.[answer.choice];
|
|
476
|
+
return decisionResult(option.event, result.usage, confidence);
|
|
477
|
+
}
|
|
478
|
+
function decisionResult(event, usage = {}, confidence) {
|
|
479
|
+
return {
|
|
480
|
+
event,
|
|
481
|
+
usage: toAgentCallUsage({
|
|
482
|
+
inputTokens: usage.inputTokens,
|
|
483
|
+
outputTokens: usage.outputTokens,
|
|
484
|
+
totalTokens: usage.totalTokens,
|
|
485
|
+
inputTokenDetails: {
|
|
486
|
+
noCacheTokens: void 0,
|
|
487
|
+
cacheReadTokens: void 0,
|
|
488
|
+
cacheWriteTokens: void 0
|
|
489
|
+
},
|
|
490
|
+
outputTokenDetails: {
|
|
491
|
+
textTokens: void 0,
|
|
492
|
+
reasoningTokens: void 0
|
|
493
|
+
}
|
|
494
|
+
}),
|
|
495
|
+
finishReason: "stop",
|
|
496
|
+
...confidence !== void 0 ? { confidence } : {}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function unofferedChoice(error, ids) {
|
|
500
|
+
if (!InvalidResponseDataError.isInstance(error) || !isRecord(error.data)) return void 0;
|
|
501
|
+
const answer = error.data[DECISION_QUESTION_ID];
|
|
502
|
+
const choice = isRecord(answer) ? answer.choice : void 0;
|
|
503
|
+
return typeof choice === "string" && !ids.includes(choice) ? choice : void 0;
|
|
504
|
+
}
|
|
505
|
+
function toDecisionState(value) {
|
|
506
|
+
if (typeof value === "string") return value;
|
|
507
|
+
try {
|
|
508
|
+
const json = JSON.parse(JSON.stringify(value ?? {}));
|
|
509
|
+
return isRecord(json) ? json : { value: json };
|
|
510
|
+
} catch {
|
|
511
|
+
return String(value);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
390
514
|
//#endregion
|
|
391
515
|
export { createAiSdkExecutors, parseModelRef, toLanguageModel };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as getAgentExecutionOptions, T as DECIDE_ACTOR, V as executorBoundLogics, at as userMessage, ot as validateSchemaSync, st as AgentError, u as canonicalEventJson, z as resolveTextLogicValue } from "./event-log-DJCMHek_.mjs";
|
|
2
2
|
import { createAsyncLogic, getNextTransitions } from "xstate";
|
|
3
3
|
//#region src/internal/is-record.ts
|
|
4
4
|
/** True for a plain object-like value (arrays excluded). @internal */
|
|
@@ -33,7 +33,8 @@ function hashString(value) {
|
|
|
33
33
|
return (hash >>> 0).toString(36);
|
|
34
34
|
}
|
|
35
35
|
function sanitizeEventToolName(eventType) {
|
|
36
|
-
const
|
|
36
|
+
const sanitizedType = eventType.replace(/[^a-zA-Z0-9_-]/g, "_") || "event";
|
|
37
|
+
const base = `${EVENT_TOOL_PREFIX}${sanitizedType}`;
|
|
37
38
|
if (base.length <= 64) return base;
|
|
38
39
|
const hash = hashString(eventType);
|
|
39
40
|
const prefixLength = 64 - hash.length - 1;
|
|
@@ -105,7 +106,8 @@ function parseAgentEvent(source, payload, options = {}) {
|
|
|
105
106
|
if (!isRecord(payload) || typeof payload.type !== "string") throw new AgentInvalidEventPayloadError("(unknown)", "expected an object with a string `type`.");
|
|
106
107
|
const event = payload;
|
|
107
108
|
if (isReservedAgentEvent(event.type)) throw new AgentInvalidEventPayloadError(event.type, "this event type is reserved for the library and cannot be sent from outside.");
|
|
108
|
-
const
|
|
109
|
+
const machine = typeof source.provide === "function" ? source : source.machine;
|
|
110
|
+
const registered = getAgentExecutionOptions(machine)?.schemas;
|
|
109
111
|
const inputSchema = (options.events ?? options.schemas?.events ?? registered?.events)?.[event.type];
|
|
110
112
|
if (!inputSchema) return event;
|
|
111
113
|
const { type, ...fields } = event;
|
|
@@ -153,6 +155,53 @@ function getAcceptedEvents(snapshot, options = {}) {
|
|
|
153
155
|
}
|
|
154
156
|
//#endregion
|
|
155
157
|
//#region src/decision.ts
|
|
158
|
+
function optionsFromPayloads(eventTypes, payloads) {
|
|
159
|
+
return eventTypes.filter((type) => !isEventPattern(type)).flatMap((type) => (payloads[type] ?? [{}]).map((payload) => ({ event: {
|
|
160
|
+
...payload,
|
|
161
|
+
type
|
|
162
|
+
} })));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The options a snapshot-aware host offers for a closed-choice request: rebuilt
|
|
166
|
+
* over the request's snapshot-resolved candidate `events` when the decide input
|
|
167
|
+
* carried `payloads` (so wildcard or omitted `allowedEvents` still get theirs),
|
|
168
|
+
* then narrowed to the events `can` accepts. `undefined` when the request is
|
|
169
|
+
* not a closed choice. Pass the result as the request's `options`;
|
|
170
|
+
* {@link resolveDecision} then rejects a choice that matches none of them.
|
|
171
|
+
* @internal
|
|
172
|
+
*/
|
|
173
|
+
function acceptedDecisionOptions(request, can) {
|
|
174
|
+
if (!request.options) return void 0;
|
|
175
|
+
const payloads = isRecord(request.input) ? request.input.payloads : void 0;
|
|
176
|
+
return (isRecord(payloads) ? optionsFromPayloads(request.events.map((event) => event.type), payloads) : request.options).filter((option) => {
|
|
177
|
+
try {
|
|
178
|
+
return can(option.event);
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
/** The offered option equal to `event` (type and payload, key order ignored). @internal */
|
|
185
|
+
function findDecisionOption(options, event) {
|
|
186
|
+
const key = canonicalEventJson(event);
|
|
187
|
+
return options.find((option) => canonicalEventJson(option.event) === key);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Stable, readable ids for options, one per option in order: `type`, then each
|
|
191
|
+
* payload value (`refund:40`). Duplicates get a `#n` suffix. Adapters use them
|
|
192
|
+
* as the labels of a single closed choice and map the answer back by index.
|
|
193
|
+
* @internal
|
|
194
|
+
*/
|
|
195
|
+
function decisionOptionIds(options) {
|
|
196
|
+
const taken = /* @__PURE__ */ new Set();
|
|
197
|
+
return options.map(({ event }) => {
|
|
198
|
+
const base = Object.entries(event).filter(([key]) => key !== "type").map(([, value]) => typeof value === "object" ? JSON.stringify(value) : String(value)).reduce((id, value) => `${id}:${value}`, event.type);
|
|
199
|
+
let id = base;
|
|
200
|
+
for (let n = 2; taken.has(id); n++) id = `${base}#${n}`;
|
|
201
|
+
taken.add(id);
|
|
202
|
+
return id;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
156
205
|
function decideRequestFromInput(input) {
|
|
157
206
|
const allowedEventTypes = resolveAllowedEventTypes(input.allowedEvents, input) ?? [];
|
|
158
207
|
return {
|
|
@@ -168,6 +217,7 @@ function decideRequestFromInput(input) {
|
|
|
168
217
|
type,
|
|
169
218
|
toolName: sanitizeEventToolName(type)
|
|
170
219
|
})),
|
|
220
|
+
...input.payloads ? { options: optionsFromPayloads(allowedEventTypes, input.payloads) } : {},
|
|
171
221
|
attempts: [],
|
|
172
222
|
temperature: input.temperature,
|
|
173
223
|
maxOutputTokens: input.maxOutputTokens,
|
|
@@ -175,7 +225,8 @@ function decideRequestFromInput(input) {
|
|
|
175
225
|
topK: input.topK,
|
|
176
226
|
seed: input.seed,
|
|
177
227
|
stopSequences: input.stopSequences,
|
|
178
|
-
metadata: input.metadata
|
|
228
|
+
metadata: input.metadata,
|
|
229
|
+
maxRetries: input.maxRetries
|
|
179
230
|
};
|
|
180
231
|
}
|
|
181
232
|
function decideActorWithExecutor(execute) {
|
|
@@ -222,7 +273,7 @@ function resolveAllowedEventTypes(allowedEvents, input) {
|
|
|
222
273
|
*
|
|
223
274
|
* export const chooseMove = createDecisionLogic({
|
|
224
275
|
* schemas: { input: z.object({ playerHp: z.number(), enemyHp: z.number() }) },
|
|
225
|
-
* model: 'openai/gpt-
|
|
276
|
+
* model: 'openai/gpt-6-luna',
|
|
226
277
|
* system: 'You are playing a turn-based game. Choose exactly one legal move.',
|
|
227
278
|
* prompt: ({ input }) => `Player HP: ${input.playerHp}\nEnemy HP: ${input.enemyHp}`,
|
|
228
279
|
* allowedEvents: ({ input }) =>
|
|
@@ -366,8 +417,8 @@ var AgentDecisionExhaustedError = class extends AgentError {
|
|
|
366
417
|
* ```
|
|
367
418
|
*/
|
|
368
419
|
function renderDecisionAttempts(request) {
|
|
369
|
-
const
|
|
370
|
-
return request.attempts.map((attempt) => userMessage(`Your previous choice failed: ${attempt.reason}. Choose again from: ${
|
|
420
|
+
const choices = (request.options ? decisionOptionIds(request.options) : request.events.map((event) => event.type)).join(", ") || "(none)";
|
|
421
|
+
return request.attempts.map((attempt) => userMessage(`Your previous choice failed: ${attempt.reason}. Choose again from: ${choices}`));
|
|
371
422
|
}
|
|
372
423
|
/**
|
|
373
424
|
* Validation + retry core for decisions. No provider mechanics — the
|
|
@@ -381,6 +432,8 @@ function renderDecisionAttempts(request) {
|
|
|
381
432
|
* event's schema), or `'rejected-by-guard'` (passes both checks but
|
|
382
433
|
* `options.canTake` returns `false` — a type/payload-legal event the
|
|
383
434
|
* machine's guard rejects right now; omit `canTake` to skip this check).
|
|
435
|
+
* When the request carries `options`, a choice matching none of them (type and
|
|
436
|
+
* payload, deep-equal) also fails as `'rejected-by-guard'`.
|
|
384
437
|
* Every prior failed attempt for this call is fed back to the executor on
|
|
385
438
|
* the next attempt via `request.attempts`, so an adapter can render "your
|
|
386
439
|
* last choice failed because X — try again" into the next model call; core
|
|
@@ -400,6 +453,11 @@ async function resolveDecision(request, executors, options = {}) {
|
|
|
400
453
|
const maxRetries = options.maxRetries ?? 2;
|
|
401
454
|
const attempts = [];
|
|
402
455
|
const eventsByType = new Map(request.events.map((event) => [event.type, event]));
|
|
456
|
+
if (request.options !== void 0 && request.options.length === 0) {
|
|
457
|
+
const error = new AgentDecisionExhaustedError([]);
|
|
458
|
+
error.message = `Decision '${request.name ?? (request.id || "agent.decide")}' has no offered option the current snapshot accepts; no model call was made.`;
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
403
461
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
404
462
|
options.signal?.throwIfAborted();
|
|
405
463
|
const result = await executor({
|
|
@@ -438,6 +496,14 @@ async function resolveDecision(request, executors, options = {}) {
|
|
|
438
496
|
continue;
|
|
439
497
|
}
|
|
440
498
|
}
|
|
499
|
+
if (request.options && !findDecisionOption(request.options, event) && !findDecisionOption(request.options, validatedEvent)) {
|
|
500
|
+
attempts.push({
|
|
501
|
+
event: validatedEvent,
|
|
502
|
+
failure: "rejected-by-guard",
|
|
503
|
+
reason: `${JSON.stringify(validatedEvent)} matched no offered option: ${decisionOptionIds(request.options).join(", ") || "(none)"}.`
|
|
504
|
+
});
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
441
507
|
if (options.canTake?.(validatedEvent) === false) {
|
|
442
508
|
attempts.push({
|
|
443
509
|
event: validatedEvent,
|
|
@@ -451,4 +517,4 @@ async function resolveDecision(request, executors, options = {}) {
|
|
|
451
517
|
throw new AgentDecisionExhaustedError(attempts);
|
|
452
518
|
}
|
|
453
519
|
//#endregion
|
|
454
|
-
export {
|
|
520
|
+
export { createDecisionRequest as a, renderDecisionAttempts as c, getAcceptedEvents as d, isEventPattern as f, createDecisionLogic as i, resolveDecision as l, isRecord as m, acceptedDecisionOptions as n, decisionOptionIds as o, parseAgentEvent as p, createDecideActor as r, isDecisionLogic as s, AgentDecisionExhaustedError as t, AgentInvalidEventPayloadError as u };
|