@superdurable/dex-agents 0.3.0 → 0.5.0

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.
Files changed (139) hide show
  1. package/README.md +598 -45
  2. package/dist/src/agent-context-compaction.d.ts +1 -1
  3. package/dist/src/agent-framework-runner.d.ts +210 -10
  4. package/dist/src/agent-framework-runner.js +30 -0
  5. package/dist/src/agent-framework-runner.js.map +1 -1
  6. package/dist/src/agent-local-directory-media-store.d.ts +111 -0
  7. package/dist/src/agent-local-directory-media-store.js +417 -0
  8. package/dist/src/agent-local-directory-media-store.js.map +1 -0
  9. package/dist/src/agent-media.d.ts +279 -0
  10. package/dist/src/agent-media.js +457 -0
  11. package/dist/src/agent-media.js.map +1 -0
  12. package/dist/src/agent-session-codecs.d.ts +3 -1
  13. package/dist/src/agent-session-codecs.js +53 -4
  14. package/dist/src/agent-session-codecs.js.map +1 -1
  15. package/dist/src/agent-session-failure.d.ts +4 -2
  16. package/dist/src/agent-session-failure.js +6 -2
  17. package/dist/src/agent-session-failure.js.map +1 -1
  18. package/dist/src/agent-session-flow-graph.d.ts +46 -0
  19. package/dist/src/agent-session-flow-graph.js +51 -0
  20. package/dist/src/agent-session-flow-graph.js.map +1 -1
  21. package/dist/src/agent-session-flow-graph.json +98 -81
  22. package/dist/src/agent-session-flow.d.ts +5 -3
  23. package/dist/src/agent-session-flow.js +10 -1
  24. package/dist/src/agent-session-flow.js.map +1 -1
  25. package/dist/src/agent-session-start.d.ts +1 -1
  26. package/dist/src/agent-session-start.js +2 -2
  27. package/dist/src/agent-session-start.js.map +1 -1
  28. package/dist/src/agent-session-steps.d.ts +9 -0
  29. package/dist/src/agent-session-steps.js +219 -34
  30. package/dist/src/agent-session-steps.js.map +1 -1
  31. package/dist/src/agent-session-tracer.js +1 -1
  32. package/dist/src/agent-session-tracer.js.map +1 -1
  33. package/dist/src/agent-session-transitions.d.ts +4 -0
  34. package/dist/src/agent-session-transitions.js +29 -1
  35. package/dist/src/agent-session-transitions.js.map +1 -1
  36. package/dist/src/agent-session-values.d.ts +112 -1
  37. package/dist/src/agent-session-values.js +6 -0
  38. package/dist/src/agent-session-values.js.map +1 -1
  39. package/dist/src/agent-session-wait-steps.js +17 -5
  40. package/dist/src/agent-session-wait-steps.js.map +1 -1
  41. package/dist/src/ai-sdk/ai-sdk-model-client.d.ts +15 -3
  42. package/dist/src/ai-sdk/ai-sdk-model-client.js +36 -7
  43. package/dist/src/ai-sdk/ai-sdk-model-client.js.map +1 -1
  44. package/dist/src/ai-sdk/index.d.ts +1 -1
  45. package/dist/src/ai-sdk/index.js +1 -1
  46. package/dist/src/ai-sdk/index.js.map +1 -1
  47. package/dist/src/{stepwise → builtin-loop}/agent-definition.d.ts +120 -5
  48. package/dist/src/builtin-loop/agent-definition.js.map +1 -0
  49. package/dist/src/builtin-loop/builtin-agent-session-flow.d.ts +69 -0
  50. package/dist/src/{stepwise/stepwise-agent-session-flow.js → builtin-loop/builtin-agent-session-flow.js} +15 -9
  51. package/dist/src/builtin-loop/builtin-agent-session-flow.js.map +1 -0
  52. package/dist/src/{stepwise/stepwise-agent-runner.d.ts → builtin-loop/builtin-loop-agent-runner.d.ts} +85 -26
  53. package/dist/src/{stepwise/stepwise-agent-runner.js → builtin-loop/builtin-loop-agent-runner.js} +320 -213
  54. package/dist/src/builtin-loop/builtin-loop-agent-runner.js.map +1 -0
  55. package/dist/src/{stepwise/stepwise-input-items.d.ts → builtin-loop/builtin-loop-input-items.d.ts} +7 -3
  56. package/dist/src/{stepwise/stepwise-input-items.js → builtin-loop/builtin-loop-input-items.js} +57 -10
  57. package/dist/src/builtin-loop/builtin-loop-input-items.js.map +1 -0
  58. package/dist/src/builtin-loop/function-tool.d.ts +282 -0
  59. package/dist/src/{stepwise → builtin-loop}/function-tool.js +63 -3
  60. package/dist/src/builtin-loop/function-tool.js.map +1 -0
  61. package/dist/src/builtin-loop/index.d.ts +8 -0
  62. package/dist/src/builtin-loop/index.js +13 -0
  63. package/dist/src/builtin-loop/index.js.map +1 -0
  64. package/dist/src/builtin-loop/json-schema-validation.js.map +1 -0
  65. package/dist/src/{stepwise → builtin-loop}/model-client.d.ts +36 -7
  66. package/dist/src/builtin-loop/model-client.js.map +1 -0
  67. package/dist/src/{stepwise → builtin-loop}/scripted-model-client.d.ts +8 -5
  68. package/dist/src/{stepwise → builtin-loop}/scripted-model-client.js +8 -5
  69. package/dist/src/builtin-loop/scripted-model-client.js.map +1 -0
  70. package/dist/src/{stepwise → builtin-loop}/tool-source.d.ts +13 -3
  71. package/dist/src/builtin-loop/tool-source.js.map +1 -0
  72. package/dist/src/connectors/connector-input-schema.d.ts +19 -0
  73. package/dist/src/connectors/connector-input-schema.js +228 -0
  74. package/dist/src/connectors/connector-input-schema.js.map +1 -0
  75. package/dist/src/connectors/connector-step-context.d.ts +94 -0
  76. package/dist/src/connectors/connector-step-context.js +180 -0
  77. package/dist/src/connectors/connector-step-context.js.map +1 -0
  78. package/dist/src/connectors/connector-tool-source.d.ts +401 -0
  79. package/dist/src/connectors/connector-tool-source.js +628 -0
  80. package/dist/src/connectors/connector-tool-source.js.map +1 -0
  81. package/dist/src/connectors/index.d.ts +3 -0
  82. package/dist/src/connectors/index.js +11 -0
  83. package/dist/src/connectors/index.js.map +1 -0
  84. package/dist/src/index.d.ts +7 -3
  85. package/dist/src/index.js +6 -3
  86. package/dist/src/index.js.map +1 -1
  87. package/dist/src/mcp/index.d.ts +1 -0
  88. package/dist/src/mcp/index.js +1 -0
  89. package/dist/src/mcp/index.js.map +1 -1
  90. package/dist/src/mcp/mcp-dex-conventions.d.ts +57 -0
  91. package/dist/src/mcp/mcp-dex-conventions.js +90 -0
  92. package/dist/src/mcp/mcp-dex-conventions.js.map +1 -0
  93. package/dist/src/mcp/mcp-image-content.d.ts +20 -0
  94. package/dist/src/mcp/mcp-image-content.js +82 -0
  95. package/dist/src/mcp/mcp-image-content.js.map +1 -0
  96. package/dist/src/mcp/mcp-tool-source.d.ts +178 -17
  97. package/dist/src/mcp/mcp-tool-source.js +237 -34
  98. package/dist/src/mcp/mcp-tool-source.js.map +1 -1
  99. package/dist/src/s3/index.d.ts +1 -0
  100. package/dist/src/s3/index.js +9 -0
  101. package/dist/src/s3/index.js.map +1 -0
  102. package/dist/src/s3/s3-media-store.d.ts +138 -0
  103. package/dist/src/s3/s3-media-store.js +255 -0
  104. package/dist/src/s3/s3-media-store.js.map +1 -0
  105. package/dist/src/stepwise/index.d.ts +41 -8
  106. package/dist/src/stepwise/index.js +35 -5
  107. package/dist/src/stepwise/index.js.map +1 -1
  108. package/dist/src/testing/agent-session-test-client.d.ts +6 -3
  109. package/dist/src/testing/agent-session-test-client.js +10 -3
  110. package/dist/src/testing/agent-session-test-client.js.map +1 -1
  111. package/dist/src/testing/scripted-model-client.d.ts +1 -1
  112. package/dist/src/testing/scripted-model-client.js +2 -2
  113. package/dist/src/testing/scripted-model-client.js.map +1 -1
  114. package/dist/src/tool-source-router.d.ts +211 -0
  115. package/dist/src/tool-source-router.js +380 -0
  116. package/dist/src/tool-source-router.js.map +1 -0
  117. package/dist/src/ui-stream/agent-event-json.d.ts +2 -1
  118. package/dist/src/ui-stream/agent-event-json.js +3 -1
  119. package/dist/src/ui-stream/agent-event-json.js.map +1 -1
  120. package/dist/src/ui-stream/agent-ui-messages.d.ts +11 -3
  121. package/dist/src/ui-stream/agent-ui-messages.js +29 -5
  122. package/dist/src/ui-stream/agent-ui-messages.js.map +1 -1
  123. package/package.json +25 -3
  124. package/dist/src/stepwise/agent-definition.js.map +0 -1
  125. package/dist/src/stepwise/function-tool.d.ts +0 -130
  126. package/dist/src/stepwise/function-tool.js.map +0 -1
  127. package/dist/src/stepwise/json-schema-validation.js.map +0 -1
  128. package/dist/src/stepwise/model-client.js.map +0 -1
  129. package/dist/src/stepwise/scripted-model-client.js.map +0 -1
  130. package/dist/src/stepwise/stepwise-agent-runner.js.map +0 -1
  131. package/dist/src/stepwise/stepwise-agent-session-flow.d.ts +0 -35
  132. package/dist/src/stepwise/stepwise-agent-session-flow.js.map +0 -1
  133. package/dist/src/stepwise/stepwise-input-items.js.map +0 -1
  134. package/dist/src/stepwise/tool-source.js.map +0 -1
  135. /package/dist/src/{stepwise → builtin-loop}/agent-definition.js +0 -0
  136. /package/dist/src/{stepwise → builtin-loop}/json-schema-validation.d.ts +0 -0
  137. /package/dist/src/{stepwise → builtin-loop}/json-schema-validation.js +0 -0
  138. /package/dist/src/{stepwise → builtin-loop}/model-client.js +0 -0
  139. /package/dist/src/{stepwise → builtin-loop}/tool-source.js +0 -0
@@ -2,7 +2,7 @@ import type { AgentEventWriter, AgentFrameworkRunner, AgentTurnContext, Deferred
2
2
  import type { AgentMessage, AgentUsage, ContextSummary, SessionSettings } from "./agent-session-values.js";
3
3
  /**
4
4
  * When a session summarizes its earlier messages so the model input stays under the context window, passed
5
- * as {@link AgentSessionOptions.compaction} or as `AgentDefinition.compaction` for a Stepwise agent.
5
+ * as {@link AgentSessionOptions.compaction} or as `AgentDefinition.compaction` for a built-in loop agent.
6
6
  *
7
7
  * Before each model call, `RunModel` asks the runner for the size of the next model input. When it reaches
8
8
  * {@link triggerRatio} of {@link contextWindowTokens}, `RunModel` makes no model call and moves to
@@ -1,6 +1,7 @@
1
+ import type { AsyncContext, RetryPolicy } from "@superdurable/dex";
1
2
  import type { BuiltInTool } from "./agent-built-in-tools.js";
2
3
  import type { ContextSummaryRequest, ContextSummaryResult } from "./agent-context-compaction.js";
3
- import type { AgentMessage, AgentPlan, AgentToolCall, AgentToolCallRecord, AgentToolCallStatus, AgentUsage, ContextSummary, SessionSettings, FrameworkMessage, InteractionMode, QuestionAnswer, ToolEffect } from "./agent-session-values.js";
4
+ import type { AgentImageReference, AgentMessage, AgentPlan, AgentToolCall, AgentToolCallRecord, AgentToolCallStatus, AgentUsage, ContextSummary, SessionSettings, FrameworkMessage, InteractionMode, QuestionAnswer, ToolEffect } from "./agent-session-values.js";
4
5
  /**
5
6
  * Per-tool override of the effect and approval defaults.
6
7
  *
@@ -134,11 +135,14 @@ export interface AgentTurnContext {
134
135
  */
135
136
  readonly messages: readonly AgentMessage[];
136
137
  /**
137
- * Framework run state from this turn's previous {@link ToolCallsRequested} outcome, or `null` for
138
- * the turn's first model call.
138
+ * Framework run state from this turn's previous {@link ToolCallsRequested} or {@link TurnContinued} outcome,
139
+ * or `null` for the turn's first model call.
139
140
  */
140
141
  readonly frameworkRunState: string | null;
141
- /** Whether this model call continues the turn after tool calls, that is, `frameworkRunState` is set. */
142
+ /**
143
+ * Whether this model call continues the turn after tool calls or after a {@link TurnContinued} outcome, that
144
+ * is, `frameworkRunState` is set.
145
+ */
142
146
  readonly isResuming: boolean;
143
147
  /**
144
148
  * Tool call records of this turn, in deferral order; every record is final. Give the framework the
@@ -241,6 +245,15 @@ export interface DeferredToolCall {
241
245
  * the tool without listing it again. Omit it to store nothing.
242
246
  */
243
247
  readonly toolSourceReference?: string;
248
+ /**
249
+ * Whether the call is a write that runs in the model's order after the turn's earlier ordered calls.
250
+ * The session starts one ordered call at a time, raises its approval only when its turn comes, and
251
+ * records later ordered calls as skipped once one does not succeed. `read` calls ignore it. When
252
+ * omitted, the call runs in parallel with the turn's other calls. `ConnectorToolSource` sets it for
253
+ * connector writes.
254
+ * @since 0.4.0
255
+ */
256
+ readonly ordered?: boolean;
244
257
  }
245
258
  /**
246
259
  * Model and response of one model call, returned in {@link TurnCompleted} or {@link ToolCallsRequested}
@@ -297,6 +310,62 @@ export interface TurnCompleted {
297
310
  readonly usage: AgentUsage;
298
311
  /** Model and response of this model call, for the `chat` span. When omitted, the span is named `chat`. */
299
312
  readonly modelCallMetadata?: ModelCallMetadata;
313
+ /**
314
+ * Error text that ends the turn as failed instead of completed.
315
+ *
316
+ * When it is set, the session still stores {@link newMessages} and records {@link usage} in the same commit.
317
+ * It then sets the turn's `lastTurnError` to this text and writes a `turn_failed` event instead of
318
+ * `turn_completed`. No retry follows, so use it only for a deliberate stop that a retry would repeat, such as a
319
+ * limit the runner enforces. Throw instead for a failure that a retry may fix. Omit it, the default, to complete
320
+ * the turn. Snapshots and the UI stream show the text, so keep secrets out of it.
321
+ *
322
+ * @since 0.5.0
323
+ */
324
+ readonly turnError?: string;
325
+ }
326
+ /**
327
+ * Outcome of a model call that answered without tool calls, after which the turn continues with another model
328
+ * call.
329
+ *
330
+ * The session stores {@link newMessages}, records {@link usage}, keeps {@link frameworkRunState}, and moves to
331
+ * `RunModel` again, all in one commit, within the same turn. That next `RunModel` is its own Step, so a retry there
332
+ * repeats only its own model call. Its {@link AgentTurnContext.messages} include these messages. End them with a
333
+ * user message when the next request must end with one, as Anthropic models from Claude 4.6 on require. The
334
+ * built-in loop returns this outcome when `AgentDefinition.decideTurnContinuation` returns a continuation, with its
335
+ * message as the last of {@link newMessages}.
336
+ *
337
+ * A runner that returns it with messages must implement {@link AgentFrameworkRunner.buildUnfinishedTurnMessages},
338
+ * as for {@link ToolCallsRequested.newMessages}; the session fails the turn otherwise. A runner that never returns
339
+ * it needs no change.
340
+ *
341
+ * @example
342
+ * ```ts
343
+ * return {
344
+ * kind: "turn_continued",
345
+ * frameworkRunState: JSON.stringify({ model_calls: modelCalls + 1 }),
346
+ * newMessages: [...previousToolResults, ...thisResponse, continuationUserMessage],
347
+ * usage,
348
+ * };
349
+ * ```
350
+ * @since 0.5.0
351
+ */
352
+ export interface TurnContinued {
353
+ /** Discriminator. */
354
+ readonly kind: "turn_continued";
355
+ /**
356
+ * Non-empty serialized framework state from which the next `RunModel` continues the turn. The session keeps it
357
+ * opaquely in `FrameworkRunState`, as it keeps {@link ToolCallsRequested.frameworkRunState}.
358
+ */
359
+ readonly frameworkRunState: string;
360
+ /**
361
+ * Messages of the turn to store now: the previous model response's unstored tool results, if any, this model
362
+ * response, and any user message for the next call. The next {@link AgentTurnContext.messages} include them.
363
+ */
364
+ readonly newMessages: readonly FrameworkMessage[];
365
+ /** Usage of this model call only. */
366
+ readonly usage: AgentUsage;
367
+ /** Model and response of this model call, for the `chat` span. When omitted, the span is named `chat`. */
368
+ readonly modelCallMetadata?: ModelCallMetadata;
300
369
  }
301
370
  /** Outcome of a model call that requested tool calls. */
302
371
  export interface ToolCallsRequested {
@@ -329,8 +398,12 @@ export interface ToolCallsRequested {
329
398
  /** Model and response of this model call, for the `chat` span. When omitted, the span is named `chat`. */
330
399
  readonly modelCallMetadata?: ModelCallMetadata;
331
400
  }
332
- /** Result of {@link AgentFrameworkRunner.runModelStep}. */
333
- export type AgentTurnOutcome = TurnCompleted | ToolCallsRequested;
401
+ /**
402
+ * Result of {@link AgentFrameworkRunner.runModelStep}.
403
+ *
404
+ * Changed in 0.5.0: {@link TurnContinued} joined the union, so code that handles every kind handles it too.
405
+ */
406
+ export type AgentTurnOutcome = TurnCompleted | ToolCallsRequested | TurnContinued;
334
407
  /** One tool call that a tool Step asks the runner to run. */
335
408
  export interface DeferredToolExecution {
336
409
  /** Flow ID, which is the session ID. */
@@ -387,11 +460,66 @@ export interface DeferredToolExecution {
387
460
  * @returns A Promise that resolves when the Worker accepted the heartbeat.
388
461
  */
389
462
  saveHeartbeatCheckpoint?(checkpoint: string): Promise<void>;
463
+ /**
464
+ * The tool Step's live context, absent when the caller has none, such as a unit test. It gives the Step
465
+ * execution ID, the attempt deadline, and heartbeats. Use it only during this call and never store it.
466
+ * Record heartbeats only through {@link heartbeat} and {@link saveHeartbeatCheckpoint}, because a bare
467
+ * `context.recordHeartbeat()` would drop the checkpoint. It is never part of stored state.
468
+ * `ConnectorToolSource` derives the connector Call ID from its Step execution ID.
469
+ * @since 0.4.0
470
+ */
471
+ readonly context?: AsyncContext;
390
472
  }
391
473
  /** Successful result of one deferred tool call. A result without `kind` is a `ToolExecutionResult`. */
392
474
  export interface ToolExecutionResult {
393
475
  /** Output text recorded on the call and shown to the model. */
394
476
  readonly output: string;
477
+ /**
478
+ * Size in bytes of the full output when {@link output} is a truncated copy; omit it when the output is
479
+ * whole. The session stores it as {@link AgentToolCall.truncatedOutputBytes}.
480
+ * @since 0.4.0
481
+ */
482
+ readonly truncatedOutputBytes?: number;
483
+ /**
484
+ * Images the tool returns with {@link output}, uploaded with `MediaStore.putImage` inside the call, at most
485
+ * `maxImagesPerMessage`. The session stores them as {@link AgentToolCall.images}; a reference that
486
+ * `validateImageReference` refuses fails the attempt. Upload before returning: a retried attempt uploads the same
487
+ * bytes to the same object. Omit it for none.
488
+ * @since 0.5.0
489
+ */
490
+ readonly images?: readonly AgentImageReference[];
491
+ /**
492
+ * Opaque text the runner keeps with the call's result for its own later model calls, such as the framework
493
+ * state that the tool changed. The session stores it as {@link AgentToolCall.runnerToolState} of the
494
+ * `succeeded` record, and `runModelStep` reads it from `AgentTurnContext.toolCalls`. The session never interprets
495
+ * it and never shows it to the model, in the browser snapshot from `agentSnapshotToJson`, or in trace attributes.
496
+ * Only the attempt that commits the result stores it, so a retried attempt never adds a second copy. Omit it to
497
+ * store nothing.
498
+ * @since 0.5.0
499
+ */
500
+ readonly runnerToolState?: string;
501
+ }
502
+ /**
503
+ * Timeout and retry policy of one tool call's Step, returned by
504
+ * {@link AgentFrameworkRunner.toolCallStepOptions}.
505
+ *
506
+ * The session applies it to the movement that starts the call's tool Step, and to no other Step. A
507
+ * `CallSingleAttemptTool` call takes only {@link executeMethodTimeout}; it still runs one attempt.
508
+ *
509
+ * @example
510
+ * ```ts
511
+ * const options: ToolCallStepOptions = {
512
+ * executeMethodTimeout: 30_000,
513
+ * executeRetry: { maximumAttempts: 3, totalDuration: 3_600_000 },
514
+ * };
515
+ * ```
516
+ * @since 0.4.0
517
+ */
518
+ export interface ToolCallStepOptions {
519
+ /** Timeout of each attempt in milliseconds; omit it for the tool Step's default. */
520
+ readonly executeMethodTimeout?: number;
521
+ /** Retry policy of a `CallTool` call; omit it for the default of three attempts. */
522
+ readonly executeRetry?: RetryPolicy;
395
523
  }
396
524
  /**
397
525
  * Result of a deferred tool call that needs an answer from the user before it can take effect.
@@ -548,6 +676,33 @@ export interface ToolTaskCancellation {
548
676
  export type DeferredToolOutcome = ToolExecutionResult | ToolInputRequested | ToolTaskStarted;
549
677
  /** Result of {@link AgentFrameworkRunner.pollToolTask}. */
550
678
  export type ToolTaskPollOutcome = ToolExecutionResult | ToolInputRequested | ToolTaskPending;
679
+ /**
680
+ * Tool failure whose effect may or may not have happened, thrown from
681
+ * {@link AgentFrameworkRunner.executeDeferredToolCall}.
682
+ *
683
+ * Throw it when the tool sent a write and cannot tell whether it took effect, such as a connector
684
+ * operation's `uncertain` branch. The session records `outcome_unknown` with this error's message as
685
+ * `error`, does not retry, and the model sees {@link outcomeUnknownToolResult}. A later ordered write of
686
+ * the turn is skipped.
687
+ *
688
+ * {@link AgentFrameworkRunner.pollToolTask} may throw it too, for a task that ended without a known
689
+ * outcome. The session then records `outcome_unknown` the same way and stops polling. Changed in 0.5.0:
690
+ * `pollToolTask` may throw it.
691
+ *
692
+ * @example
693
+ * ```ts
694
+ * throw new ToolOutcomeUnknownError("the provider timed out after receiving the refund");
695
+ * ```
696
+ * @since 0.4.0
697
+ */
698
+ export declare class ToolOutcomeUnknownError extends Error {
699
+ /**
700
+ * Creates an unknown-outcome failure.
701
+ * @param message - Error text recorded on the call.
702
+ * @param options - Standard error options, such as `cause`.
703
+ */
704
+ constructor(message: string, options?: ErrorOptions);
705
+ }
551
706
  /**
552
707
  * Tool failure that the model should see, thrown from {@link AgentFrameworkRunner.executeDeferredToolCall}.
553
708
  *
@@ -629,12 +784,35 @@ export interface AgentFrameworkRunner<ToolOutcome extends DeferredToolOutcome =
629
784
  * @returns The framework message, with `role` `"user"`.
630
785
  */
631
786
  buildUserMessage(text: string): FrameworkMessage;
787
+ /**
788
+ * Converts a user message with images into the framework's user message. Optional.
789
+ *
790
+ * Implement it when the runner can show images to its model. `send_user_message` accepts images only for a runner
791
+ * with this method, and the session calls it instead of {@link buildUserMessage} for a message with images. Put
792
+ * the references, never the bytes, in `frameworkItem`; read the bytes with `loadImages` when the model call
793
+ * builds its input. The session sets the stored message's `images` itself. It must be synchronous,
794
+ * deterministic, and free of side effects.
795
+ *
796
+ * @example
797
+ * ```ts
798
+ * public buildUserMessageWithImages(text: string, images: readonly AgentImageReference[]): FrameworkMessage {
799
+ * return { role: "user", text, toolCallId: null, toolName: null, toolArguments: null, toolOutput: null,
800
+ * frameworkItem: JSON.stringify({ role: "user", content: text, images }) };
801
+ * }
802
+ * ```
803
+ * @param text - User message text, never blank.
804
+ * @param images - The message's images, at least one, each valid for `validateImageReference`.
805
+ * @returns The framework message, with `role` `"user"`.
806
+ * @since 0.5.0
807
+ */
808
+ buildUserMessageWithImages?(text: string, images: readonly AgentImageReference[]): FrameworkMessage;
632
809
  /**
633
810
  * Makes the turn's next model call, at most one.
634
811
  *
635
812
  * @param turn - Turn context; see {@link AgentTurnContext}.
636
- * @returns {@link TurnCompleted} with every new message of the turn, or {@link ToolCallsRequested}
637
- * with the framework run state and every tool call of the model response.
813
+ * @returns {@link TurnCompleted} with every new message of the turn, {@link ToolCallsRequested}
814
+ * with the framework run state and every tool call of the model response, or {@link TurnContinued} to make
815
+ * another model call within the same turn.
638
816
  * @throws Any error to retry the model call; after the last attempt the turn fails.
639
817
  */
640
818
  runModelStep(turn: AgentTurnContext): Promise<AgentTurnOutcome>;
@@ -671,8 +849,8 @@ export interface AgentFrameworkRunner<ToolOutcome extends DeferredToolOutcome =
671
849
  * Such a runner reads {@link AgentTurnContext.interactionMode} in every `runModelStep`. In a `planning`
672
850
  * turn it offers only the `write_todos` definition from {@link builtInToolDefinitions}. It may force
673
851
  * `write_todos` on the turn's first model call while the plan is not a draft yet, where the model accepts a
674
- * forced tool choice; Stepwise does with `planningToolChoice: "required"`. In every
675
- * turn it shows the plan with {@link buildPlanNote} as user text before the turn's messages. The Stepwise
852
+ * forced tool choice; the built-in loop does with `planningToolChoice: "required"`. In every
853
+ * turn it shows the plan with {@link buildPlanNote} as user text before the turn's messages. The built-in loop
676
854
  * runner implements it; a framework adapter that cannot change the tools it offers between turns leaves
677
855
  * plan mode off.
678
856
  */
@@ -701,6 +879,9 @@ export interface AgentFrameworkRunner<ToolOutcome extends DeferredToolOutcome =
701
879
  * the call's result.
702
880
  * @throws {ToolExecutionError} When the task failed in a way the model should see; the call is
703
881
  * recorded as `failed`.
882
+ * @throws {ToolOutcomeUnknownError} When the task ended and may or may not have taken effect; the call is
883
+ * recorded as `outcome_unknown` and not polled again. Changed in 0.5.0: this error is recorded instead of
884
+ * failing the poll attempt.
704
885
  * @throws Any other error to fail the poll attempt; after three attempts the record becomes `failed`
705
886
  * for a `read` call and `outcome_unknown` otherwise, because the task may still run.
706
887
  */
@@ -727,6 +908,25 @@ export interface AgentFrameworkRunner<ToolOutcome extends DeferredToolOutcome =
727
908
  * three times, then records the call `outcome_unknown` anyway, so the turn always ends.
728
909
  */
729
910
  cancelToolTask?(cancellation: ToolTaskCancellation): Promise<void>;
911
+ /**
912
+ * Returns the timeout and retry policy of one call's tool Step. Optional.
913
+ *
914
+ * Implement it when some tools need options other than the tool Steps' defaults, as the built-in loop runner
915
+ * does for a tool source that defines `toolCallStepOptions`. The session calls it whenever it starts a
916
+ * call's tool Step, including after an approval, so it must answer from the record alone. A method that
917
+ * throws leaves the defaults.
918
+ *
919
+ * @example
920
+ * ```ts
921
+ * public toolCallStepOptions(toolCall: AgentToolCall): ToolCallStepOptions | undefined {
922
+ * return toolCall.toolName === "export_report" ? { executeMethodTimeout: 120_000 } : undefined;
923
+ * }
924
+ * ```
925
+ * @param toolCall - The record whose tool Step starts.
926
+ * @returns The options, or `undefined` for the defaults.
927
+ * @since 0.4.0
928
+ */
929
+ toolCallStepOptions?(toolCall: AgentToolCall): ToolCallStepOptions | undefined;
730
930
  /**
731
931
  * Estimates the size of the model input that the next {@link runModelStep} sends. Optional; one of the
732
932
  * three compaction methods that {@link AgentSessionOptions.compaction} requires.
@@ -29,6 +29,36 @@ export function resolveToolPolicy(toolName, toolPolicies, defaultToolEffect, fra
29
29
  const requiresApproval = policy?.requiresApproval ?? (effect === "non_idempotent_write" || effect === "unclassified");
30
30
  return { effect, requiresApproval };
31
31
  }
32
+ /**
33
+ * Tool failure whose effect may or may not have happened, thrown from
34
+ * {@link AgentFrameworkRunner.executeDeferredToolCall}.
35
+ *
36
+ * Throw it when the tool sent a write and cannot tell whether it took effect, such as a connector
37
+ * operation's `uncertain` branch. The session records `outcome_unknown` with this error's message as
38
+ * `error`, does not retry, and the model sees {@link outcomeUnknownToolResult}. A later ordered write of
39
+ * the turn is skipped.
40
+ *
41
+ * {@link AgentFrameworkRunner.pollToolTask} may throw it too, for a task that ended without a known
42
+ * outcome. The session then records `outcome_unknown` the same way and stops polling. Changed in 0.5.0:
43
+ * `pollToolTask` may throw it.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * throw new ToolOutcomeUnknownError("the provider timed out after receiving the refund");
48
+ * ```
49
+ * @since 0.4.0
50
+ */
51
+ export class ToolOutcomeUnknownError extends Error {
52
+ /**
53
+ * Creates an unknown-outcome failure.
54
+ * @param message - Error text recorded on the call.
55
+ * @param options - Standard error options, such as `cause`.
56
+ */
57
+ constructor(message, options) {
58
+ super(message, options);
59
+ this.name = "ToolOutcomeUnknownError";
60
+ }
61
+ }
32
62
  /**
33
63
  * Tool failure that the model should see, thrown from {@link AgentFrameworkRunner.executeDeferredToolCall}.
34
64
  *
@@ -1 +1 @@
1
- {"version":3,"file":"agent-framework-runner.js","sourceRoot":"","sources":["../../src/agent-framework-runner.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,kDAAkD;AAClD,mEAAmE;AACnE,+CAA+C;AAC/C,EAAE;AACF,0DAA0D;AAmD1D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAgB,EAChB,YAAkD,EAClD,iBAA6B,EAC7B,eAA4B;IAE5B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1F,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,eAAe,IAAI,iBAAiB,CAAC;IACtE,MAAM,gBAAgB,GACpB,MAAM,EAAE,gBAAgB,IAAI,CAAC,MAAM,KAAK,sBAAsB,IAAI,MAAM,KAAK,cAAc,CAAC,CAAC;IAC/F,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACtC,CAAC;AAogBD;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C;;;;OAIG;IACH,YAAmB,OAAe,EAAE,OAAsB;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AA+ND;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gCAAgC,CAC9C,WAA+B;IAE/B,IAAI,WAAW,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,WAAW,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"agent-framework-runner.js","sourceRoot":"","sources":["../../src/agent-framework-runner.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,kDAAkD;AAClD,mEAAmE;AACnE,+CAA+C;AAC/C,EAAE;AACF,0DAA0D;AAsD1D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAgB,EAChB,YAAkD,EAClD,iBAA6B,EAC7B,eAA4B;IAE5B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1F,MAAM,MAAM,GAAG,MAAM,EAAE,MAAM,IAAI,eAAe,IAAI,iBAAiB,CAAC;IACtE,MAAM,gBAAgB,GACpB,MAAM,EAAE,gBAAgB,IAAI,CAAC,MAAM,KAAK,sBAAsB,IAAI,MAAM,KAAK,cAAc,CAAC,CAAC;IAC/F,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACtC,CAAC;AAqoBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD;;;;OAIG;IACH,YAAmB,OAAe,EAAE,OAAsB;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C;;;;OAIG;IACH,YAAmB,OAAe,EAAE,OAAsB;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AA4QD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gCAAgC,CAC9C,WAA+B;IAE/B,IAAI,WAAW,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,WAAW,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,111 @@
1
+ import { type MediaStore } from "./agent-media.js";
2
+ import type { AgentImageReference } from "./agent-session-values.js";
3
+ /**
4
+ * Keeps image bytes in a local directory, one file per distinct image.
5
+ *
6
+ * Use it when every client and Worker of a session runs on one machine, such as a command-line tool in local mode;
7
+ * use `S3MediaStore` from `@superdurable/dex-agents/s3` when they run on different machines.
8
+ *
9
+ * The file of an image is `<root>/sha256/<sha256>`, and a reference's `uri` is `file://<root>/sha256/<sha256>`, where
10
+ * `<root>` is the absolute root with every UTF-8 byte other than ASCII letters, digits, `-`, `.`, `_`, `~`, and `/`
11
+ * written as `%XX` in uppercase hex. The root is made absolute as text: `.` and `..` segments and repeated or trailing
12
+ * `/` characters are resolved without reading the file system, symbolic links are not resolved, and Unicode is not
13
+ * normalized. A client and a Worker that pass the same root therefore build the same references, in every core.
14
+ *
15
+ * `putImage` copies the bytes into a temporary file in the same directory, syncs it, and renames it into place, so a
16
+ * reader never sees a partial image. An existing file with the same bytes is success, so retried and concurrent uploads
17
+ * of the same bytes leave one file; an existing file with other bytes is replaced. Directories the store creates get
18
+ * mode `0700` and image files mode `0600`, narrowed further by the process umask. When `putImage` writes the image, it
19
+ * syncs the file before the rename. Before every `putImage` returns, including when an identical file already exists
20
+ * and nothing is written, it syncs the `sha256` directory, the root, and the root's parent. It also syncs the parent of
21
+ * each directory it creates. Directories above the root's parent must already be durable: a concurrent creator of a
22
+ * deeper missing ancestor is out of scope.
23
+ *
24
+ * The store refuses a `sha256` directory or an image file that is a symbolic link or not a regular file, and an image
25
+ * file with more than one hard link, with `MediaStoreError`, on both upload and read. The root itself may be a symbolic
26
+ * link. Node.js has no `openat`, so this store opens files by path and checks before and after each operation: it
27
+ * checks the `sha256` directory with `lstat` and opens the file with `O_NOFOLLOW`, then confirms that the real path of
28
+ * the `sha256` directory is still the root's and that the path still names the opened or written file. A read that
29
+ * fails the check returns nothing, and a write that fails it removes the file it wrote when it can still find it. A
30
+ * process of the same user that can write the `0700` root can still race a directory swap between these checks; the
31
+ * Python core opens every path relative to an open directory and has no such window. `getImage` reads only
32
+ * references under this store's root, refuses a file whose size differs from the reference before reading it, reads at
33
+ * most one byte more than `sizeBytes`, and checks the bytes with `verifyImageBytes`. It needs a POSIX file system; on
34
+ * Windows the constructor throws.
35
+ *
36
+ * Session deletion does not delete files. A runner reads a session's images again whenever a model call replays them,
37
+ * so keep every file at least as long as any session that references it can still run a turn or replay its history. A
38
+ * missing file makes those turns fail with `MediaStoreError`.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { LocalDirectoryMediaStore } from "@superdurable/dex-agents";
43
+ *
44
+ * const store = new LocalDirectoryMediaStore("/var/lib/my-agent/media");
45
+ * const reference = await store.putImage(screenshotPng);
46
+ * // reference.uri === "file:///var/lib/my-agent/media/sha256/9f2c..."
47
+ * ```
48
+ * @since 0.5.0
49
+ */
50
+ export declare class LocalDirectoryMediaStore implements MediaStore {
51
+ /** The absolute root directory, made absolute as text, without a trailing `/`. */
52
+ readonly root: string;
53
+ /** The location that every reference of this store starts with: `"file://<root>"`, with the root percent-encoded. */
54
+ readonly storeUri: string;
55
+ /**
56
+ * Creates a store; it touches no file until the first upload or read.
57
+ *
58
+ * @param root - Absolute path of the directory that holds the images, such as `"/var/lib/my-agent/media"`. The first
59
+ * upload creates it and any missing parent directory with mode `0700`.
60
+ * @throws {TypeError} When `root` is not absolute, is not well-formed Unicode, contains NUL, or is the file system
61
+ * root.
62
+ * @throws {RangeError} When `root` makes image URIs longer than `maxImageUriLength`.
63
+ * @throws {Error} On a system without POSIX file access, such as Windows.
64
+ */
65
+ constructor(root: string);
66
+ /**
67
+ * Returns the path of the file of the image with this SHA-256.
68
+ *
69
+ * @param sha256 - SHA-256 of the bytes as 64 lowercase hex digits.
70
+ * @returns `"<root>/sha256/<sha256>"`.
71
+ */
72
+ imagePath(sha256: string): string;
73
+ /**
74
+ * Stores one image unless the directory already has it, and returns its reference.
75
+ *
76
+ * @param data - PNG, JPEG, GIF, or WebP bytes, at most `maxImageBytes`.
77
+ * @returns The reference.
78
+ * @throws {ImageValidationError} When the bytes are not a valid image for `buildImageReference`. Nothing is written.
79
+ * @throws {MediaStoreError} When the `sha256` directory or the image's file is a symbolic link or not a directory or
80
+ * regular file, the image's file has more than one hard link, or the `sha256` directory changed during the write.
81
+ * @throws A Node.js system error (`NodeJS.ErrnoException`) when the file system refuses a write, such as for missing
82
+ * permissions or a full disk. Retrying is safe.
83
+ */
84
+ putImage(data: Uint8Array): Promise<AgentImageReference>;
85
+ /**
86
+ * Returns the bytes of one image in this store.
87
+ *
88
+ * @param reference - A reference under this store's root.
89
+ * @returns The exact bytes, checked with `verifyImageBytes`.
90
+ * @throws {ImageValidationError} When the reference's fields are invalid.
91
+ * @throws {MediaStoreError} When the reference is outside this store's root, the file is missing, is a symbolic link
92
+ * or not a regular file, has more than one hard link, moved during the read, or its bytes do not match.
93
+ * @throws A Node.js system error (`NodeJS.ErrnoException`) when the file system refuses the read for another reason.
94
+ */
95
+ getImage(reference: AgentImageReference): Promise<Uint8Array>;
96
+ private imageDirectory;
97
+ /** Creates the root and its missing parents with mode `0700`, syncing the parent of each. */
98
+ private makeRoot;
99
+ /** Throws `MediaStoreError` unless the `sha256` directory is a real directory, not a symbolic link. */
100
+ private requireImageDirectory;
101
+ /** Returns whether the image's file exists and holds exactly `data`. */
102
+ private hasImageFile;
103
+ /** Opens the reference's image file, refusing symbolic links, special files, hard links, and moved files. */
104
+ private openImageFile;
105
+ /** Returns the open file, or `undefined` when a rename replaced it after the open. */
106
+ private openImageFileOnce;
107
+ /** Returns whether the `sha256` directory is a real directory whose real path is the root's. */
108
+ private isImageDirectoryInStore;
109
+ private directoryChanged;
110
+ private replaceImageFile;
111
+ }