@bastani/atomic 0.9.27 → 0.9.28-alpha.2
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 +61 -0
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/broker/broker.ts +13 -1
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +3 -3
- package/dist/builtin/web-access/index.bundle.mjs +194 -44
- package/dist/builtin/web-access/package.json +3 -3
- package/dist/builtin/workflows/CHANGELOG.md +31 -0
- package/dist/builtin/workflows/builtin/{chunk-gw7rghda.js → chunk-aqv8ny38.js} +257 -259
- package/dist/builtin/workflows/builtin/{chunk-gts8wgjr.js → chunk-br4ac9a3.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-9bhs02gm.js → chunk-p3xmajmj.js} +439 -40
- package/dist/builtin/workflows/builtin/chunk-p6jagvyf.js +8 -0
- package/dist/builtin/workflows/builtin/goal.js +2 -2
- package/dist/builtin/workflows/builtin/index.js +4 -4
- package/dist/builtin/workflows/builtin/ralph.js +2 -2
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/src/authoring.d.ts +14 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +108110 -107201
- package/dist/builtin/workflows/src/index.js +42226 -41077
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +16 -11
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +16 -1
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +12 -5
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/credential-print.d.ts +1 -1
- package/dist/cli/credential-print.d.ts.map +1 -1
- package/dist/cli/credential-print.js +2 -56
- package/dist/cli/credential-print.js.map +1 -1
- package/dist/codemode-worker.js +99 -3
- package/dist/core/agent-session-accessors.d.ts.map +1 -1
- package/dist/core/agent-session-accessors.js +6 -1
- package/dist/core/agent-session-accessors.js.map +1 -1
- package/dist/core/agent-session-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-compaction.js +20 -3
- package/dist/core/agent-session-compaction.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +3 -2
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-extension-bindings.d.ts +6 -0
- package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
- package/dist/core/agent-session-extension-bindings.js +96 -12
- package/dist/core/agent-session-extension-bindings.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +4 -1
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +19 -3
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-state.d.ts.map +1 -1
- package/dist/core/agent-session-state.js +3 -2
- package/dist/core/agent-session-state.js.map +1 -1
- package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
- package/dist/core/agent-session-tool-registry.js +11 -16
- package/dist/core/agent-session-tool-registry.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +1 -0
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +5 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage-backends.d.ts.map +1 -1
- package/dist/core/auth-storage-backends.js +2 -1
- package/dist/core/auth-storage-backends.js.map +1 -1
- package/dist/core/auth-storage.d.ts +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +15 -7
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/azure-provider-compat.d.ts +8 -0
- package/dist/core/azure-provider-compat.d.ts.map +1 -0
- package/dist/core/azure-provider-compat.js +65 -0
- package/dist/core/azure-provider-compat.js.map +1 -0
- package/dist/core/child-session-options.d.ts +1 -1
- package/dist/core/child-session-options.d.ts.map +1 -1
- package/dist/core/child-session-options.js +11 -2
- package/dist/core/child-session-options.js.map +1 -1
- package/dist/core/compaction/classifier-compaction.d.ts +13 -0
- package/dist/core/compaction/classifier-compaction.d.ts.map +1 -0
- package/dist/core/compaction/classifier-compaction.js +110 -0
- package/dist/core/compaction/classifier-compaction.js.map +1 -0
- package/dist/core/compaction/compaction-boundary.d.ts.map +1 -1
- package/dist/core/compaction/compaction-boundary.js +2 -6
- package/dist/core/compaction/compaction-boundary.js.map +1 -1
- package/dist/core/compaction/compaction-runner.d.ts +16 -1
- package/dist/core/compaction/compaction-runner.d.ts.map +1 -1
- package/dist/core/compaction/compaction-runner.js +97 -12
- package/dist/core/compaction/compaction-runner.js.map +1 -1
- package/dist/core/compaction/compaction-types.d.ts +18 -1
- package/dist/core/compaction/compaction-types.d.ts.map +1 -1
- package/dist/core/compaction/compaction-types.js +1 -1
- package/dist/core/compaction/compaction-types.js.map +1 -1
- package/dist/core/compaction/fallback-planner.d.ts +1 -0
- package/dist/core/compaction/fallback-planner.d.ts.map +1 -1
- package/dist/core/compaction/fallback-planner.js +2 -0
- package/dist/core/compaction/fallback-planner.js.map +1 -1
- package/dist/core/compaction/index.d.ts +3 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +3 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/model-resolver.d.ts +16 -0
- package/dist/core/compaction/model-resolver.d.ts.map +1 -0
- package/dist/core/compaction/model-resolver.js +18 -0
- package/dist/core/compaction/model-resolver.js.map +1 -0
- package/dist/core/compaction/morph-compaction.d.ts +14 -0
- package/dist/core/compaction/morph-compaction.d.ts.map +1 -0
- package/dist/core/compaction/morph-compaction.js +99 -0
- package/dist/core/compaction/morph-compaction.js.map +1 -0
- package/dist/core/compaction/pi-summary-fallback.d.ts +34 -0
- package/dist/core/compaction/pi-summary-fallback.d.ts.map +1 -0
- package/dist/core/compaction/pi-summary-fallback.js +357 -0
- package/dist/core/compaction/pi-summary-fallback.js.map +1 -0
- package/dist/core/compaction/planner-outcome.d.ts +10 -6
- package/dist/core/compaction/planner-outcome.d.ts.map +1 -1
- package/dist/core/compaction/planner-outcome.js +11 -4
- package/dist/core/compaction/planner-outcome.js.map +1 -1
- package/dist/core/compaction/range-planner-diagnostics.d.ts +3 -1
- package/dist/core/compaction/range-planner-diagnostics.d.ts.map +1 -1
- package/dist/core/compaction/range-planner-diagnostics.js +11 -0
- package/dist/core/compaction/range-planner-diagnostics.js.map +1 -1
- package/dist/core/compaction/range-planner.d.ts +3 -3
- package/dist/core/compaction/range-planner.d.ts.map +1 -1
- package/dist/core/compaction/range-planner.js +59 -28
- package/dist/core/compaction/range-planner.js.map +1 -1
- package/dist/core/compaction/region-trimming.d.ts.map +1 -1
- package/dist/core/compaction/region-trimming.js +7 -0
- package/dist/core/compaction/region-trimming.js.map +1 -1
- package/dist/core/compaction/structured-compaction-input.d.ts +19 -0
- package/dist/core/compaction/structured-compaction-input.d.ts.map +1 -0
- package/dist/core/compaction/structured-compaction-input.js +70 -0
- package/dist/core/compaction/structured-compaction-input.js.map +1 -0
- package/dist/core/compaction/transcript-serialization.d.ts +1 -0
- package/dist/core/compaction/transcript-serialization.d.ts.map +1 -1
- package/dist/core/compaction/transcript-serialization.js +26 -0
- package/dist/core/compaction/transcript-serialization.js.map +1 -1
- package/dist/core/extensions/api-types.d.ts +3 -2
- package/dist/core/extensions/api-types.d.ts.map +1 -1
- package/dist/core/extensions/api-types.js.map +1 -1
- package/dist/core/extensions/context-types.d.ts +1 -0
- package/dist/core/extensions/context-types.d.ts.map +1 -1
- package/dist/core/extensions/context-types.js.map +1 -1
- package/dist/core/extensions/loader-api.d.ts.map +1 -1
- package/dist/core/extensions/loader-api.js +5 -0
- package/dist/core/extensions/loader-api.js.map +1 -1
- package/dist/core/extensions/loader-host-modules.d.ts +1 -0
- package/dist/core/extensions/loader-host-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-host-modules.js +12 -0
- package/dist/core/extensions/loader-host-modules.js.map +1 -1
- package/dist/core/extensions/runner-context.d.ts +10 -4
- package/dist/core/extensions/runner-context.d.ts.map +1 -1
- package/dist/core/extensions/runner-context.js +47 -3
- package/dist/core/extensions/runner-context.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +11 -4
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +38 -9
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/tool-types.d.ts +2 -0
- package/dist/core/extensions/tool-types.d.ts.map +1 -1
- package/dist/core/extensions/tool-types.js.map +1 -1
- package/dist/core/extensions/workflow-events.d.ts +49 -0
- package/dist/core/extensions/workflow-events.d.ts.map +1 -1
- package/dist/core/extensions/workflow-events.js.map +1 -1
- package/dist/core/extensions/workflow-run-control-hub.d.ts +16 -4
- package/dist/core/extensions/workflow-run-control-hub.d.ts.map +1 -1
- package/dist/core/extensions/workflow-run-control-hub.js +72 -1
- package/dist/core/extensions/workflow-run-control-hub.js.map +1 -1
- package/dist/core/extensions/workflow-run-control.d.ts +11 -3
- package/dist/core/extensions/workflow-run-control.d.ts.map +1 -1
- package/dist/core/extensions/workflow-run-control.js.map +1 -1
- package/dist/core/fast-model-variants.d.ts +1 -1
- package/dist/core/fast-model-variants.js +1 -1
- package/dist/core/fast-model-variants.js.map +1 -1
- package/dist/core/keybindings.d.ts +4 -4
- package/dist/core/messages.d.ts +1 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +5 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +2 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-fallback-failures.d.ts.map +1 -1
- package/dist/core/model-fallback-failures.js +5 -1
- package/dist/core/model-fallback-failures.js.map +1 -1
- package/dist/core/model-resolver-cli.d.ts.map +1 -1
- package/dist/core/model-resolver-cli.js +5 -1
- package/dist/core/model-resolver-cli.js.map +1 -1
- package/dist/core/model-resolver-defaults.js +1 -1
- package/dist/core/model-resolver-defaults.js.map +1 -1
- package/dist/core/model-resolver-initial.d.ts.map +1 -1
- package/dist/core/model-resolver-initial.js +6 -1
- package/dist/core/model-resolver-initial.js.map +1 -1
- package/dist/core/model-resolver-patterns.d.ts.map +1 -1
- package/dist/core/model-resolver-patterns.js +2 -1
- package/dist/core/model-resolver-patterns.js.map +1 -1
- package/dist/core/model-resolver-scope.d.ts.map +1 -1
- package/dist/core/model-resolver-scope.js +3 -1
- package/dist/core/model-resolver-scope.js.map +1 -1
- package/dist/core/model-runtime-streaming.d.ts.map +1 -1
- package/dist/core/model-runtime-streaming.js +2 -0
- package/dist/core/model-runtime-streaming.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +48 -22
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/provider-composer-internal.d.ts.map +1 -1
- package/dist/core/provider-composer-internal.js +4 -1
- package/dist/core/provider-composer-internal.js.map +1 -1
- package/dist/core/provider-display-names.js +1 -1
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +3 -0
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader-core.d.ts +1 -0
- package/dist/core/resource-loader-core.d.ts.map +1 -1
- package/dist/core/resource-loader-core.js +4 -0
- package/dist/core/resource-loader-core.js.map +1 -1
- package/dist/core/resource-loader-internals.d.ts +1 -0
- package/dist/core/resource-loader-internals.d.ts.map +1 -1
- package/dist/core/resource-loader-internals.js.map +1 -1
- package/dist/core/resource-loader-reload.d.ts.map +1 -1
- package/dist/core/resource-loader-reload.js +28 -5
- package/dist/core/resource-loader-reload.js.map +1 -1
- package/dist/core/resource-loader-types.d.ts +2 -0
- package/dist/core/resource-loader-types.d.ts.map +1 -1
- package/dist/core/resource-loader-types.js.map +1 -1
- package/dist/core/sdk-types.d.ts +6 -4
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +5 -3
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-lifecycle-work.d.ts +2 -1
- package/dist/core/session-lifecycle-work.d.ts.map +1 -1
- package/dist/core/session-lifecycle-work.js +15 -1
- package/dist/core/session-lifecycle-work.js.map +1 -1
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +4 -2
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts +2 -0
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js +33 -4
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/core/settings-manager-core.d.ts.map +1 -1
- package/dist/core/settings-manager-core.js +2 -0
- package/dist/core/settings-manager-core.js.map +1 -1
- package/dist/core/settings-storage.d.ts.map +1 -1
- package/dist/core/settings-storage.js +2 -1
- package/dist/core/settings-storage.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -0
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/skills.d.ts +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +3 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts +3 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +7 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tool-loadout.d.ts.map +1 -1
- package/dist/core/tool-loadout.js +8 -1
- package/dist/core/tool-loadout.js.map +1 -1
- package/dist/core/tool-selection.d.ts +13 -0
- package/dist/core/tool-selection.d.ts.map +1 -0
- package/dist/core/tool-selection.js +43 -0
- package/dist/core/tool-selection.js.map +1 -0
- package/dist/core/tools/read.d.ts +7 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +11 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/transcript.d.ts +58 -0
- package/dist/core/transcript.d.ts.map +1 -0
- package/dist/core/transcript.js +185 -0
- package/dist/core/transcript.js.map +1 -0
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +2 -1
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/index.d.ts.map +1 -1
- package/dist/extensions/codemode/index.js +1 -0
- package/dist/extensions/codemode/index.js.map +1 -1
- package/dist/extensions/codemode/tool.d.ts +4 -1
- package/dist/extensions/codemode/tool.d.ts.map +1 -1
- package/dist/extensions/codemode/tool.js +6 -3
- package/dist/extensions/codemode/tool.js.map +1 -1
- package/dist/extensions/herdr/index.d.ts.map +1 -1
- package/dist/extensions/herdr/index.js +15 -10
- package/dist/extensions/herdr/index.js.map +1 -1
- package/dist/extensions/herdr/pane-owner.d.ts +6 -2
- package/dist/extensions/herdr/pane-owner.d.ts.map +1 -1
- package/dist/extensions/herdr/pane-owner.js +19 -4
- package/dist/extensions/herdr/pane-owner.js.map +1 -1
- package/dist/extensions/mcp/client/oauth/flow.d.ts.map +1 -1
- package/dist/extensions/mcp/client/oauth/flow.js +14 -1
- package/dist/extensions/mcp/client/oauth/flow.js.map +1 -1
- package/dist/extensions/mcp/client/oauth/types.d.ts +1 -0
- package/dist/extensions/mcp/client/oauth/types.d.ts.map +1 -1
- package/dist/extensions/mcp/client/oauth/types.js.map +1 -1
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/runtime.d.ts +5 -2
- package/dist/extensions/mcp/runtime.d.ts.map +1 -1
- package/dist/extensions/mcp/runtime.js +18 -4
- package/dist/extensions/mcp/runtime.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/dist/migrations-azure.d.ts +4 -0
- package/dist/migrations-azure.d.ts.map +1 -0
- package/dist/migrations-azure.js +41 -0
- package/dist/migrations-azure.js.map +1 -0
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +2 -0
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.js +2 -1
- package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.d.ts +2 -0
- package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.js +9 -3
- package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
- package/dist/modes/interactive/components/compaction-boundary-message.d.ts +2 -0
- package/dist/modes/interactive/components/compaction-boundary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-boundary-message.js +15 -3
- package/dist/modes/interactive/components/compaction-boundary-message.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-items.js +43 -0
- package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector-types.d.ts +6 -0
- package/dist/modes/interactive/components/settings-selector-types.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector-types.js.map +1 -1
- package/dist/modes/interactive/interactive-prompt-turn.js +6 -4
- package/dist/modes/interactive/interactive-prompt-turn.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +13 -1
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/interactive/interactive-selectors.js +8 -0
- package/dist/modes/interactive/interactive-selectors.js.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.js +2 -0
- package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +1 -1
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive/theme/tui-theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/tui-theme.js +1 -0
- package/dist/modes/interactive/theme/tui-theme.js.map +1 -1
- package/dist/modes/interactive-engine/engine-args.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-args.js +1 -0
- package/dist/modes/interactive-engine/engine-args.js.map +1 -1
- package/dist/utils/credential-redaction.d.ts +2 -0
- package/dist/utils/credential-redaction.d.ts.map +1 -0
- package/dist/utils/credential-redaction.js +20 -0
- package/dist/utils/credential-redaction.js.map +1 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +6 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/docs/codemode.md +3 -2
- package/docs/compaction/reference.md +10 -4
- package/docs/compaction.md +40 -8
- package/docs/docs.json +1 -0
- package/docs/extensions/api-reference.md +18 -0
- package/docs/extensions/authoring.md +1 -1
- package/docs/extensions/events.md +2 -2
- package/docs/extensions.md +2 -0
- package/docs/keybindings.md +9 -9
- package/docs/mcp-servers.md +4 -0
- package/docs/providers.md +33 -3
- package/docs/reference/cli.md +10 -3
- package/docs/sdk/reference.md +16 -1
- package/docs/sdk/web-server.md +325 -0
- package/docs/sdk.md +83 -1
- package/docs/security.md +1 -1
- package/docs/workflows/api-reference.md +74 -4
- package/docs/workflows/authoring.md +6 -2
- package/docs/workflows/builtins.md +4 -0
- package/docs/workflows/operations.md +12 -4
- package/npm-shrinkwrap.json +78 -74
- package/package.json +9 -9
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Embedding Atomic in a web server"
|
|
3
|
+
sidebarTitle: "Web server"
|
|
4
|
+
description: "Run Atomic sessions and workflows behind a Next.js App Router route and stream their output to a browser."
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Embedding Atomic in a web server
|
|
8
|
+
|
|
9
|
+
This guide puts an Atomic agent behind an HTTP endpoint, using a Next.js App Router project as the example. The same pattern works in any long-running Node.js server: keep one session per conversation, start a prompt per request, and stream the session's events back as newline-delimited JSON (NDJSON).
|
|
10
|
+
|
|
11
|
+
Read the [SDK](/sdk) page first for `createAgentSession()` and session events.
|
|
12
|
+
|
|
13
|
+
The examples use `createTranscript()`, `createAgentSessionAdapter()`, and `RunOpts.onStageSessionEvent`, which require an Atomic release newer than 0.9.28-alpha.1. Check the [changelog](/changelog) for your installed version.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @bastani/atomic
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Atomic needs Node.js 22.19 or newer and does not require dependency install scripts.
|
|
22
|
+
|
|
23
|
+
Bun blocks the install scripts of some dependencies, such as `@embedded-postgres/<platform>`, `protobufjs`, and `@google/genai`, and reports them after `bun add @bastani/atomic`. You don't need to add them to `trustedDependencies` in `package.json`: Atomic prepares the embedded Postgres runtime itself the first time it starts it, and it doesn't depend on the other scripts.
|
|
24
|
+
|
|
25
|
+
## Keep Atomic out of the server bundle
|
|
26
|
+
|
|
27
|
+
Atomic finds its bundled resources and native modules relative to its installed package, so it must load from `node_modules` rather than from Next.js's server bundle. List it in `serverExternalPackages` (Next.js 15 and later):
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// next.config.ts
|
|
31
|
+
import type { NextConfig } from "next";
|
|
32
|
+
|
|
33
|
+
const nextConfig: NextConfig = {
|
|
34
|
+
serverExternalPackages: ["@bastani/atomic", "@bastani/atomic-natives", "@bastani/pi-ai", "typebox"],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default nextConfig;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Atomic's own dependencies load from `node_modules` once `@bastani/atomic` is external. The other entries cover packages your app may import directly, such as `typebox` for workflow schemas, so your code and Atomic share one copy. Add any other Atomic dependency you import yourself.
|
|
41
|
+
|
|
42
|
+
Every route that imports Atomic must use the Node.js runtime:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
export const runtime = "nodejs";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Import Atomic only from server code. Client components may use `import type` from `@bastani/atomic`, which adds nothing to the browser bundle.
|
|
49
|
+
|
|
50
|
+
## Keep one session per conversation
|
|
51
|
+
|
|
52
|
+
An `AgentSession` holds the conversation's history, so create one per conversation and reuse it across requests. Keep sessions on `globalThis`: in `next dev`, module-level variables reset on every hot reload, but `globalThis` survives, so open conversations don't vanish and old sessions aren't leaked. Restart the dev server after changing session options; existing sessions keep the options they were created with.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// lib/atomic.ts
|
|
56
|
+
import "server-only";
|
|
57
|
+
import { type AgentSession, createAgentSession, ModelRuntime, SessionManager } from "@bastani/atomic";
|
|
58
|
+
|
|
59
|
+
export const WORKSPACE = process.env.AGENT_WORKSPACE ?? "/srv/agent-workspace";
|
|
60
|
+
|
|
61
|
+
export interface Conversation {
|
|
62
|
+
session: AgentSession;
|
|
63
|
+
busy: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface AtomicState {
|
|
67
|
+
modelRuntime: Promise<ModelRuntime>;
|
|
68
|
+
conversations: Map<string, Promise<Conversation>>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const shared = globalThis as typeof globalThis & { atomicState?: AtomicState };
|
|
72
|
+
const state = (shared.atomicState ??= {
|
|
73
|
+
modelRuntime: ModelRuntime.create(),
|
|
74
|
+
conversations: new Map(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export function getConversation(id: string): Promise<Conversation> {
|
|
78
|
+
let conversation = state.conversations.get(id);
|
|
79
|
+
if (!conversation) {
|
|
80
|
+
conversation = state.modelRuntime.then(async (modelRuntime) => {
|
|
81
|
+
const { session } = await createAgentSession({
|
|
82
|
+
cwd: WORKSPACE,
|
|
83
|
+
sessionManager: SessionManager.inMemory(WORKSPACE),
|
|
84
|
+
modelRuntime,
|
|
85
|
+
tools: ["read", "find", "search"],
|
|
86
|
+
builtins: { workflows: false, subagents: false, mcp: false, "web-access": false, intercom: false },
|
|
87
|
+
});
|
|
88
|
+
return { session, busy: false };
|
|
89
|
+
});
|
|
90
|
+
conversation.catch(() => state.conversations.delete(id));
|
|
91
|
+
state.conversations.set(id, conversation);
|
|
92
|
+
}
|
|
93
|
+
return conversation;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function closeConversation(id: string): Promise<void> {
|
|
97
|
+
const conversation = state.conversations.get(id);
|
|
98
|
+
state.conversations.delete(id);
|
|
99
|
+
if (conversation) await (await conversation).session.dispose();
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`SessionManager.inMemory()` keeps history in process memory. To keep conversations across restarts, pass a file-backed `SessionManager` instead; see [Sessions](/sessions). Call `closeConversation()` when a conversation ends or has been idle for a while, because each open session holds resources until it is disposed.
|
|
104
|
+
|
|
105
|
+
Sessions on `globalThis` live in one server process. Run this on a long-lived Node.js server, and route each conversation to the same instance if you run several. Short-lived serverless functions lose the session between requests.
|
|
106
|
+
|
|
107
|
+
## Stream a prompt
|
|
108
|
+
|
|
109
|
+
The route below starts one prompt and streams the assistant's output as NDJSON. `createTranscript()` turns session events into ordered, JSON-serializable parts (text, thinking, and tool calls joined to their results), so each line carries the complete output so far and the client simply replaces what it shows.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// app/api/chat/route.ts
|
|
113
|
+
import { createTranscript, type TranscriptPart } from "@bastani/atomic";
|
|
114
|
+
import { getConversation } from "@/lib/atomic";
|
|
115
|
+
import { authenticate } from "@/lib/auth";
|
|
116
|
+
|
|
117
|
+
export const runtime = "nodejs";
|
|
118
|
+
|
|
119
|
+
export type ChatLine =
|
|
120
|
+
| { type: "parts"; parts: TranscriptPart[] }
|
|
121
|
+
| { type: "done" }
|
|
122
|
+
| { type: "error"; message: string };
|
|
123
|
+
|
|
124
|
+
export async function POST(request: Request): Promise<Response> {
|
|
125
|
+
const user = await authenticate(request);
|
|
126
|
+
if (!user) return new Response("Unauthorized", { status: 401 });
|
|
127
|
+
|
|
128
|
+
const { conversationId, message } = (await request.json()) as { conversationId: string; message: string };
|
|
129
|
+
const conversation = await getConversation(`${user.id}:${conversationId}`);
|
|
130
|
+
const { session } = conversation;
|
|
131
|
+
if (conversation.busy || session.isStreaming) {
|
|
132
|
+
return new Response("This conversation is already answering a prompt.", { status: 409 });
|
|
133
|
+
}
|
|
134
|
+
if (request.signal.aborted) return new Response(null, { status: 499 });
|
|
135
|
+
conversation.busy = true;
|
|
136
|
+
|
|
137
|
+
const encoder = new TextEncoder();
|
|
138
|
+
const transcript = createTranscript();
|
|
139
|
+
let open = true;
|
|
140
|
+
|
|
141
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
142
|
+
start(controller) {
|
|
143
|
+
const send = (line: ChatLine) => {
|
|
144
|
+
if (open) controller.enqueue(encoder.encode(`${JSON.stringify(line)}\n`));
|
|
145
|
+
};
|
|
146
|
+
const stop = () => void session.abort();
|
|
147
|
+
request.signal.addEventListener("abort", stop, { once: true });
|
|
148
|
+
|
|
149
|
+
const unsubscribe = session.subscribe((event) => {
|
|
150
|
+
transcript.apply(event);
|
|
151
|
+
if (
|
|
152
|
+
event.type === "message_update" ||
|
|
153
|
+
event.type === "message_end" ||
|
|
154
|
+
event.type === "tool_execution_update" ||
|
|
155
|
+
event.type === "tool_execution_end"
|
|
156
|
+
) {
|
|
157
|
+
send({ type: "parts", parts: transcript.parts() });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
session
|
|
162
|
+
.prompt(message)
|
|
163
|
+
.then(() => send({ type: "done" }))
|
|
164
|
+
.catch((error: Error) => send({ type: "error", message: error.message }))
|
|
165
|
+
.finally(() => {
|
|
166
|
+
unsubscribe();
|
|
167
|
+
request.signal.removeEventListener("abort", stop);
|
|
168
|
+
conversation.busy = false;
|
|
169
|
+
if (open) {
|
|
170
|
+
open = false;
|
|
171
|
+
controller.close();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
cancel() {
|
|
176
|
+
open = false;
|
|
177
|
+
void session.abort();
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return new Response(stream, {
|
|
182
|
+
headers: { "Content-Type": "application/x-ndjson; charset=utf-8", "Cache-Control": "no-cache" },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
A few details matter here:
|
|
188
|
+
|
|
189
|
+
- **One prompt at a time.** `session.prompt()` rejects while the session is already streaming. The route answers `409` instead. `busy` is set synchronously, so two requests that arrive together can't both start a prompt. To queue a message instead of rejecting it, call `session.prompt(message, { streamingBehavior: "followUp" })`.
|
|
190
|
+
- **Stop on disconnect.** When the browser disconnects or aborts its `fetch`, `request.signal` aborts and the stream is cancelled. Both call `session.abort()`, which stops the current turn and its tools but keeps the session usable.
|
|
191
|
+
- **Payload size.** Each line repeats the whole output so far. For long answers, send lines on a timer instead of on every event, or send only the parts that changed.
|
|
192
|
+
|
|
193
|
+
`createTranscript()` reports a tool result's text by default. To send a tool's structured `details` instead, pass `createTranscript({ toolResult: (toolName) => (toolName === "todo" ? "details" : "content") })`.
|
|
194
|
+
|
|
195
|
+
## Read the stream in the browser
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// app/chat/send-message.ts
|
|
199
|
+
import type { TranscriptPart } from "@bastani/atomic";
|
|
200
|
+
import type { ChatLine } from "@/app/api/chat/route";
|
|
201
|
+
|
|
202
|
+
export async function sendMessage(
|
|
203
|
+
conversationId: string,
|
|
204
|
+
message: string,
|
|
205
|
+
onParts: (parts: TranscriptPart[]) => void,
|
|
206
|
+
signal?: AbortSignal,
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
const response = await fetch("/api/chat", {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: { "Content-Type": "application/json" },
|
|
211
|
+
body: JSON.stringify({ conversationId, message }),
|
|
212
|
+
signal,
|
|
213
|
+
});
|
|
214
|
+
if (!response.ok || !response.body) throw new Error(await response.text());
|
|
215
|
+
|
|
216
|
+
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
217
|
+
let buffered = "";
|
|
218
|
+
for (;;) {
|
|
219
|
+
const { value, done } = await reader.read();
|
|
220
|
+
if (done) return;
|
|
221
|
+
buffered += value;
|
|
222
|
+
const lines = buffered.split("\n");
|
|
223
|
+
buffered = lines.pop() ?? "";
|
|
224
|
+
for (const text of lines) {
|
|
225
|
+
if (!text) continue;
|
|
226
|
+
const line = JSON.parse(text) as ChatLine;
|
|
227
|
+
if (line.type === "parts") onParts(line.parts);
|
|
228
|
+
if (line.type === "error") throw new Error(line.message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Render each part by `type`: `text` and `thinking` are strings, and a `toolCall` part has `name`, `arguments`, and, once the tool reports progress or finishes, a `result` with `content`, `isError`, and `isPartial`. Pass an `AbortController`'s signal to stop the agent from the page.
|
|
235
|
+
|
|
236
|
+
## Run a workflow from a route
|
|
237
|
+
|
|
238
|
+
`run()` from `@bastani/atomic/workflows` runs a workflow definition in the server process. Each stage runs as an in-process Atomic session, so `run()` needs no adapters. Use `createAgentSessionAdapter()` when every stage should share session options, such as the same tool restrictions as your chat sessions:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// app/api/review/route.ts
|
|
242
|
+
import { createTranscript, type Transcript } from "@bastani/atomic";
|
|
243
|
+
import { createAgentSessionAdapter, run } from "@bastani/atomic/workflows";
|
|
244
|
+
import { WORKSPACE } from "@/lib/atomic";
|
|
245
|
+
import { authenticate } from "@/lib/auth";
|
|
246
|
+
import { reviewWorkflow } from "@/workflows/review";
|
|
247
|
+
|
|
248
|
+
export const runtime = "nodejs";
|
|
249
|
+
|
|
250
|
+
export async function POST(request: Request): Promise<Response> {
|
|
251
|
+
if (!(await authenticate(request))) return new Response("Unauthorized", { status: 401 });
|
|
252
|
+
const { topic } = (await request.json()) as { topic: string };
|
|
253
|
+
if (request.signal.aborted) return new Response(null, { status: 499 });
|
|
254
|
+
|
|
255
|
+
const encoder = new TextEncoder();
|
|
256
|
+
const abort = new AbortController();
|
|
257
|
+
request.signal.addEventListener("abort", () => abort.abort(), { once: true });
|
|
258
|
+
|
|
259
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
260
|
+
async start(controller) {
|
|
261
|
+
const send = (line: object) => {
|
|
262
|
+
if (!abort.signal.aborted) controller.enqueue(encoder.encode(`${JSON.stringify(line)}\n`));
|
|
263
|
+
};
|
|
264
|
+
const stages = new Map<string, Transcript>();
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const result = await run(reviewWorkflow, { topic }, {
|
|
268
|
+
cwd: WORKSPACE,
|
|
269
|
+
signal: abort.signal,
|
|
270
|
+
durability: { mode: "memory" },
|
|
271
|
+
adapters: {
|
|
272
|
+
agentSession: createAgentSessionAdapter({ cwd: WORKSPACE, tools: ["read", "find", "search"] }),
|
|
273
|
+
},
|
|
274
|
+
onStageSessionEvent: (runId, stageId, event) => {
|
|
275
|
+
let transcript = stages.get(stageId);
|
|
276
|
+
if (!transcript) stages.set(stageId, (transcript = createTranscript()));
|
|
277
|
+
transcript.apply(event);
|
|
278
|
+
if (event.type === "message_update" || event.type === "tool_execution_end") {
|
|
279
|
+
send({ type: "stage", runId, stageId, parts: transcript.parts() });
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
send({ type: "done", status: result.status, result: result.result, error: result.error });
|
|
284
|
+
} catch (error) {
|
|
285
|
+
send({ type: "error", message: error instanceof Error ? error.message : String(error) });
|
|
286
|
+
} finally {
|
|
287
|
+
if (!abort.signal.aborted) controller.close();
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
cancel() {
|
|
291
|
+
abort.abort();
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return new Response(stream, { headers: { "Content-Type": "application/x-ndjson; charset=utf-8" } });
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`onStageSessionEvent` receives every stage's session events tagged with its run and stage ids, including stages that switch to a fallback model and stages of nested workflows. Aborting `signal`, here when the client disconnects, cancels the run.
|
|
300
|
+
|
|
301
|
+
### Choose workflow durability
|
|
302
|
+
|
|
303
|
+
By default, `run()` makes workflow state durable by starting a managed local Postgres, so a run can resume after the process exits. Request-scoped servers rarely want that:
|
|
304
|
+
|
|
305
|
+
- **`durability: { mode: "memory" }`** keeps state in memory for this call only. No Postgres starts, and the run can't resume after the process exits. Use it when the request owns the run.
|
|
306
|
+
- **`durability: { mode: "durable", systemDatabaseUrl }`** stores state in a Postgres database you run, such as a hosted one, and Atomic doesn't start its own. Use it in deployments that need runs to survive restarts. `DBOS_SYSTEM_DATABASE_URL`, when set, overrides the URL.
|
|
307
|
+
|
|
308
|
+
One process uses one workflow database. See [Choosing the durable backend](/workflows/api-reference#choosing-the-durable-backend) for the full rules, and [Workflows](/workflows) for writing workflow definitions.
|
|
309
|
+
|
|
310
|
+
## Secure the endpoint
|
|
311
|
+
|
|
312
|
+
A route that drives an agent session lets whoever calls it run tools on your server with the server process's permissions. Atomic has [no built-in sandbox](/security#no-built-in-sandbox).
|
|
313
|
+
|
|
314
|
+
- **Require authentication** on every route that creates sessions, sends prompts, or starts workflows, and scope conversation ids to the authenticated user, as `getConversation()` does above.
|
|
315
|
+
- **Allow only the tools you need.** `tools` is an allowlist; the examples allow only the read-only `read`, `find`, and `search`. Leave out `bash`, `edit`, and `write` unless the agent must change files or run commands. `excludedTools` removes tools, and `noTools: "all"` exposes none. `ask_user_question` needs a person to answer, so leave it out of headless sessions. See [Tools](/sdk/reference#tools).
|
|
316
|
+
- **Disable builtins you don't use.** `builtins` turns off the shipped workflows, subagents, MCP, web access, and Intercom packages, along with their tools.
|
|
317
|
+
- **Choose the working directory deliberately.** `cwd` sets the starting directory for tool paths and controls which project settings, context files, and extensions Atomic discovers. It does not restrict file access: tools can use absolute paths and paths outside `cwd`. Point it at a directory you control, never at an upload or a user-supplied path.
|
|
318
|
+
- **Isolate the process** in a container, VM, or restricted OS account when callers must not access other server files, even with read-only tools, or when the agent can write files or run commands. See [Containerization](/containerization).
|
|
319
|
+
- **Keep credentials on the server.** Provider keys come from the server's `ModelRuntime`. Never send them to the browser or accept them from requests.
|
|
320
|
+
|
|
321
|
+
## Next steps
|
|
322
|
+
|
|
323
|
+
- [SDK](/sdk) for sessions, prompting, and events.
|
|
324
|
+
- [SDK API reference](/sdk/reference) for every `createAgentSession()` option.
|
|
325
|
+
- [Workflow API reference](/workflows/api-reference) for `run()` and `RunOpts`.
|
package/docs/sdk.md
CHANGED
|
@@ -26,6 +26,8 @@ For a custom host that runs background work, see [Owner-bound task supervisor](/
|
|
|
26
26
|
|
|
27
27
|
Not sure the SDK is the right integration mode? Compare it with RPC and JSON mode on [Programmatic use](/programmatic).
|
|
28
28
|
|
|
29
|
+
To serve an agent over HTTP, see [Embedding Atomic in a web server](/sdk/web-server), which walks through a Next.js App Router route that streams session output to a browser.
|
|
30
|
+
|
|
29
31
|
## Quick Start
|
|
30
32
|
|
|
31
33
|
```typescript
|
|
@@ -241,6 +243,7 @@ interface SessionWorkflows {
|
|
|
241
243
|
pause(target: { all: true }): Promise<WorkflowRunControlOutcome>;
|
|
242
244
|
quit(target: string | { all: true }): Promise<WorkflowRunControlOutcome>;
|
|
243
245
|
resume(runId: string, options?: { stageId?: string; message?: string }): Promise<WorkflowRunControlOutcome>;
|
|
246
|
+
observe(observer: WorkflowActivityObserver): WorkflowActivitySubscription;
|
|
244
247
|
}
|
|
245
248
|
```
|
|
246
249
|
|
|
@@ -313,7 +316,38 @@ async function onResumeClicked() {
|
|
|
313
316
|
}
|
|
314
317
|
```
|
|
315
318
|
|
|
316
|
-
`pause` holds the live run and keeps it resumable in this process; `quit` retires the executor at a durability boundary so a later process can resume it. After `quit`, or when resuming a run from a previous process, `resume` follows the durable resume path: keep the workflow definition and durable storage available. See [pausing, quitting, and resuming](/workflows/operations#pausing-quitting-and-resuming) for what each action guarantees.
|
|
319
|
+
`pause` holds the live run and keeps it resumable in this process; `quit` retires the executor at a durability boundary so a later process can resume it. After `quit`, or when resuming a run from a previous process, `resume` follows the durable resume path: keep the workflow definition and durable storage available. See [pausing, quitting, and resuming](/workflows/operations#pausing-quitting-and-resuming) for what each action guarantees.
|
|
320
|
+
|
|
321
|
+
#### Observing workflow runs
|
|
322
|
+
|
|
323
|
+
To follow runs instead of polling `listRuns()`, call `session.workflows.observe(observer)`. It covers runs that the agent launches with the `workflow` tool, and needs no extension. The first frame is a `snapshot`. After it come `changed` and `removed` frames, delivered in order. Each callback finishes before the next one starts. Call `dispose()` on the returned subscription to stop. The frames and cursors follow the [workflow activity contract](/extensions/events#workflow-activity-and-lifecycle-hooks).
|
|
324
|
+
|
|
325
|
+
Each root summary carries a `graph` with the nodes of that root's workflow, including the nodes of nested child workflows. Nodes are `stage` nodes and `ctx.tool` nodes. Each one has an `id`, `kind`, `name`, `status`, `parentIds`, `runId`, `nodeId` (its run-local id), `depth`, and `executionOrder` when it is known. Tool nodes add their `ordinal`. A stage that is waiting on a prompt adds `pendingPrompt` with the prompt's `id`, `kind`, `message`, `choices`, and `createdAt`. A prompt that a run raises outside any stage appears in `graph.runPrompts` with the same fields and the `runId` that owns it. A node status change produces a `changed` frame. Frames are plain JSON-serializable objects.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import type { WorkflowRootActivity } from "@bastani/atomic";
|
|
329
|
+
|
|
330
|
+
const roots = new Map<string, WorkflowRootActivity>();
|
|
331
|
+
const subscription = session.workflows.observe((frame) => {
|
|
332
|
+
if (frame.kind === "snapshot") {
|
|
333
|
+
roots.clear();
|
|
334
|
+
// `recovering` or `unavailable` means activity is unknown, not idle.
|
|
335
|
+
if (frame.availability === "ready") for (const root of frame.roots) roots.set(root.rootRunId, root);
|
|
336
|
+
} else if (frame.kind === "changed") {
|
|
337
|
+
roots.set(frame.root.rootRunId, frame.root);
|
|
338
|
+
for (const node of frame.root.graph?.nodes ?? []) {
|
|
339
|
+
renderNode(node.id, node.name, node.status, node.parentIds);
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
roots.delete(frame.rootRunId);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Later:
|
|
347
|
+
subscription.dispose();
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Observation can start before the workflows package has finished loading; the first snapshot then reports `availability: "unavailable"`, and a `ready` snapshot follows. The subscription survives `session.reload()`. After a reload, the observer receives a new snapshot with a new cursor `epoch`, so replace everything you knew. When the session is disposed, each observer receives a final `snapshot` with `availability: "unavailable"` before `dispose()` resolves; after that, `observe()` throws `WorkflowRunControlUnavailableError`. This stream reports root activity and graph state. It does not carry the events inside each stage.
|
|
317
351
|
|
|
318
352
|
### Workflow and subagent children
|
|
319
353
|
|
|
@@ -403,6 +437,8 @@ A `ShutdownFailed` error contains component failures in `errors`; cleanup still
|
|
|
403
437
|
|
|
404
438
|
Captured extension APIs also refuse new execution, mutations and registrations as soon as close or reload begins. Already-admitted `pi.exec()` calls remain part of the awaited drain; provide `signal` or `timeout` when invoking subprocesses that might not finish on their own. `abort()` alone does not retire the API. A rejected transactional reload restores the surviving generation's action admission.
|
|
405
439
|
|
|
440
|
+
Custom messages queued with `persistWhenStreaming: true` survive a successful reload and receive their model turn through the replacement extensions. An explicitly paused queue remains paused; call `resumeQueuedMessages()` before sending the next prompt. A rejected transactional reload retains the notices for the next prompt on the surviving generation.
|
|
441
|
+
|
|
406
442
|
#### Finishing admitted work
|
|
407
443
|
|
|
408
444
|
Disposal and reload wait for admitted callbacks, resource refreshes, and subprocesses. Ensure callbacks can settle independently of disposal, including work started by cleanup handlers. Give `pi.exec()` calls a timeout or cancellation signal, and await them in the handler so you can inspect failures. Never make cleanup wait for disposal itself.
|
|
@@ -415,6 +451,24 @@ Strict transactional reload failure leaves the original generation usable. Ordin
|
|
|
415
451
|
|
|
416
452
|
Prefer acquiring extension resources in `session_start`. If a factory acquires them earlier, register `session_shutdown` immediately, even if `extensionsOverride` may later omit that factory. Capture cleanup handles when acquiring resources rather than rereading loader getters during cleanup.
|
|
417
453
|
|
|
454
|
+
Transactional reload dispatches candidate `session_start` before commit. Use `publishExtensionContextEffect(ctx, effect)` to publish shared lifecycle state only after commit and retiring-generation cleanup; rollback discards the effect. Outside a transactional candidate, it runs immediately. Awaiting the helper queues the effect, not its eventual execution, so startup must not depend on the effect's result.
|
|
455
|
+
|
|
456
|
+
For synchronous identity handoff, use `publishExtensionContextEffect(ctx, effect, "commit")`. This phase runs after the transaction commits and before predecessor authority is revoked or any retirement work is awaited. It must not return a promise or start asynchronous work; asynchronous callbacks are rejected and reported. The default `"start"` phase still runs after retirement, when reporter and task-host startup is safe.
|
|
457
|
+
|
|
458
|
+
Register generation-owned observer disposal with `registerExtensionContextRetirementEffect(ctx, dispose)` during `session_start`. Disposal is synchronous, runs before authority revocation and before rollback invalidation, and is independent of `session_shutdown` handler order. The returned function unregisters the effect. Keep awaited resource cleanup in `session_shutdown`; observer disposal must not quit or checkpoint preserved workflows.
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
import { publishExtensionContextEffect, type ExtensionFactory } from "@bastani/atomic";
|
|
462
|
+
|
|
463
|
+
const extension: ExtensionFactory = (pi) => {
|
|
464
|
+
pi.on("session_start", (_event, ctx) =>
|
|
465
|
+
publishExtensionContextEffect(ctx, () => {
|
|
466
|
+
pi.events.emit("example:session-ready", { sessionId: ctx.sessionManager.getSessionId() });
|
|
467
|
+
}),
|
|
468
|
+
);
|
|
469
|
+
};
|
|
470
|
+
```
|
|
471
|
+
|
|
418
472
|
Do not cache dialog functions across reload attempts. Retiring functions refuse new questions; after a rejected transaction, use the surviving session's current `ctx.ui`.
|
|
419
473
|
|
|
420
474
|
Shared loaders, event buses, settings, and `SessionManager` instances do not share live task ownership. Closing one session leaves another session's commands and borrowed discovery resources alone.
|
|
@@ -678,6 +732,34 @@ To rebuild an assistant message from deltas, accumulate them in your own message
|
|
|
678
732
|
|
|
679
733
|
If you subscribe part-way through a turn, seed your object from `session.agent.state.streamingMessage`, if present, to include the deltas you missed.
|
|
680
734
|
|
|
735
|
+
### Collecting assistant output
|
|
736
|
+
|
|
737
|
+
`createTranscript()` does that bookkeeping for you. It turns session events into the assistant's output as ordered, JSON-serializable parts:
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
import { createTranscript } from "@bastani/atomic";
|
|
741
|
+
|
|
742
|
+
const transcript = createTranscript();
|
|
743
|
+
session.subscribe((event) => transcript.apply(event));
|
|
744
|
+
|
|
745
|
+
await session.prompt("List the files here");
|
|
746
|
+
console.log(JSON.stringify(transcript.parts()));
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
`parts()` returns a fresh copy of the parts of every assistant message seen so far, including one still streaming:
|
|
750
|
+
|
|
751
|
+
- `{ type: "text", text }`
|
|
752
|
+
- `{ type: "thinking", thinking }`. Redacted thinking is left out.
|
|
753
|
+
- `{ type: "toolCall", id, name, arguments, result? }`. `result` appears once the tool reports progress or finishes. It holds `isError` and `isPartial`, which stays `true` until the tool finishes. If the message is aborted or fails, tool calls without a final result get an error result carrying the error message.
|
|
754
|
+
|
|
755
|
+
Events from tools called inside other tools (those with `parentToolCallId`) are ignored. By default, `result.content` holds the result's text content. To get a tool's structured `details` instead, pass `toolResult`:
|
|
756
|
+
|
|
757
|
+
```typescript
|
|
758
|
+
const transcript = createTranscript({
|
|
759
|
+
toolResult: (toolName) => (toolName === "todo" ? "details" : "content"),
|
|
760
|
+
});
|
|
761
|
+
```
|
|
762
|
+
|
|
681
763
|
## Options Reference
|
|
682
764
|
|
|
683
765
|
Moved to [SDK API reference](/sdk/reference#options-reference).
|
package/docs/security.md
CHANGED
|
@@ -21,7 +21,7 @@ Trusting a project allows Atomic to load trust-gated project inputs, including:
|
|
|
21
21
|
- missing project packages configured through project settings
|
|
22
22
|
- project-local extensions and project package-managed extensions
|
|
23
23
|
|
|
24
|
-
Declining trust skips protected resources, including project-local discovery of `AGENTS.override.md`, `AGENTS.md`, and `CLAUDE.md`. Global context and explicitly supplied CLI resources remain available.
|
|
24
|
+
Declining trust skips protected resources, including project-local discovery of `AGENTS.override.md`, `AGENTS.md`, and `CLAUDE.md`. Global context and explicitly supplied CLI resources remain available. Interactive sessions in an untrusted project show a warning that project resources are ignored; use `/trust` to save a decision, then restart Atomic.
|
|
25
25
|
|
|
26
26
|
Before resolving trust, Atomic loads only user/global extensions and explicit CLI `-e` package-level extensions. Those trusted extensions can handle the `project_trust` event; the first to return a yes/no decision owns it.
|
|
27
27
|
|
|
@@ -1163,18 +1163,29 @@ interface StageSnapshot extends WorkflowSerializableObject {
|
|
|
1163
1163
|
readonly id: string;
|
|
1164
1164
|
readonly name: string;
|
|
1165
1165
|
readonly status: StageStatus;
|
|
1166
|
+
readonly parentIds: readonly string[];
|
|
1167
|
+
readonly executionOrder?: number;
|
|
1168
|
+
readonly model?: string;
|
|
1169
|
+
readonly startedAt?: number;
|
|
1170
|
+
readonly endedAt?: number;
|
|
1171
|
+
readonly durationMs?: number;
|
|
1166
1172
|
readonly result?: WorkflowSerializableValue;
|
|
1167
1173
|
readonly error?: string;
|
|
1174
|
+
readonly routerSelection?: ModelRouterOutput;
|
|
1168
1175
|
}
|
|
1169
1176
|
```
|
|
1170
1177
|
|
|
1171
1178
|
Programmatic `run(...)` returns this type. `exited` identifies `ctx.exit(...)` termination, and `stages` contains the final stage snapshots.
|
|
1172
1179
|
|
|
1180
|
+
Each stage snapshot carries its graph position: `parentIds` lists the stages it depends on, and `executionOrder` is its admission order among the run's stages and tool nodes. `model` is the effective model after fallback resolution; `routerSelection` is the automatic routing choice. Timestamps are epoch milliseconds and are absent until the stage starts or ends. The runtime may replace `parentIds` before a stage starts, so read it from the latest snapshot rather than caching it.
|
|
1181
|
+
|
|
1173
1182
|
## Programmatic usage
|
|
1174
1183
|
|
|
1175
1184
|
`@bastani/atomic/workflows` is Atomic's published workflow SDK. Import `workflow` from that specifier, import `Type` from `typebox`, and export the definition returned by `workflow({...})`. Workflow helpers may also import the host TypeBox runtime subpaths `typebox/compile` and `typebox/value`; the legacy `@sinclair/typebox` root and matching subpaths remain supported. Keep runtime helpers such as widget factories and shared utilities in a subdirectory outside the top-level discovery scan, such as `.atomic/workflows/lib/`; see [Workflow Locations](/workflows/operations#workflow-locations).
|
|
1176
1185
|
|
|
1177
|
-
Package authors list both `@bastani/atomic` and `typebox` in `peerDependencies`. The `@bastani/atomic` package publishes compiled JavaScript and declarations for `@bastani/atomic/workflows`, `@bastani/atomic/workflows/builtin`, and each `@bastani/atomic/workflows/builtin/*` module. TypeScript resolves those exports directly under `moduleResolution: NodeNext`. When Atomic executes a workflow file or an imported helper, its runtime loader resolves the workflow SDK and supported TypeBox aliases to the same in-memory host modules.
|
|
1186
|
+
Package authors list both `@bastani/atomic` and `typebox` in `peerDependencies`. The `@bastani/atomic` package publishes compiled JavaScript and declarations for `@bastani/atomic/workflows`, `@bastani/atomic/workflows/builtin`, and each `@bastani/atomic/workflows/builtin/*` module. TypeScript resolves those exports directly under `moduleResolution: NodeNext`. When Atomic executes a workflow file or an imported helper, its runtime loader resolves the workflow SDK and supported TypeBox aliases to the same in-memory host modules.
|
|
1187
|
+
|
|
1188
|
+
The loader also shares the host's modules for the exact specifiers `@bastani/atomic`, `@bastani/pi-ai`, and `@bastani/pi-ai/providers/all`. These exceptions expose Atomic's public root API and the AI compatibility API and provider transports. The host's versions take precedence over project-local copies, including in standalone-binary installs without project packages. Workflow files and relative imports still refresh on reload. Other deep subpaths resolve from disk. The workflow loader does not share the extension loader's remaining host aliases, including the separate agent-core and UI packages, other AI subpaths, and `proper-lockfile`. Older or alternative hosts without [`getWorkflowHostModules()`](/extensions/api-reference#pi-getworkflowhostmodules) retain filesystem resolution for the three specifiers.
|
|
1178
1189
|
|
|
1179
1190
|
```ts
|
|
1180
1191
|
import { workflow } from "@bastani/atomic/workflows";
|
|
@@ -1273,6 +1284,52 @@ function run<
|
|
|
1273
1284
|
|
|
1274
1285
|
Executes a compiled definition programmatically with validated inputs. Empty-input workflows accept an empty readonly record.
|
|
1275
1286
|
|
|
1287
|
+
#### Stage sessions and adapters
|
|
1288
|
+
|
|
1289
|
+
When `opts.adapters` supplies neither `agentSession` nor `prompt`, `run()` uses `createAgentSessionAdapter({ cwd: opts.cwd })`. Each stage then runs its own in-process Atomic `createAgentSession` session with an in-memory `SessionManager`, unless the stage sets `sessionDir` or resumes or forks a session. Stage sessions resolve models, credentials, settings, and resources the same way `createAgentSession` does. A `prompt` adapter on its own answers stage prompts itself, so `run()` adds no default session adapter beside it.
|
|
1290
|
+
|
|
1291
|
+
```typescript
|
|
1292
|
+
function createAgentSessionAdapter(
|
|
1293
|
+
baseOptions?: Omit<CreateAgentSessionOptions, "sessionManager">,
|
|
1294
|
+
): AgentSessionAdapter;
|
|
1295
|
+
|
|
1296
|
+
interface AgentSessionAdapter {
|
|
1297
|
+
create(
|
|
1298
|
+
options: StageSessionCreateOptions,
|
|
1299
|
+
meta?: StageExecutionMeta,
|
|
1300
|
+
): Promise<StageSessionRuntime | StageSessionCreateResult>;
|
|
1301
|
+
}
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
Call `createAgentSessionAdapter(baseOptions)` yourself to apply the same `createAgentSession` options, such as `agentDir`, `modelRuntime`, or `settingsManager`, to every stage. Options set on an individual stage override `baseOptions`. `baseOptions` cannot set `sessionManager`, because every stage needs its own. A `cwd` in `baseOptions` applies to every stage that doesn't set its own, and session files saved through `sessionDir` record that directory.
|
|
1305
|
+
|
|
1306
|
+
A custom adapter's `create(options, meta)` receives the stage's session options, including any session manager Atomic opened for `sessionDir`, resume, or fork. It must resolve to `{ session }` or to the session itself, where `session` implements `StageSessionRuntime`. To change options per stage while keeping the stage policy that `createAgentSessionAdapter` applies, such as hiding the `workflow` tool from stage sessions, delegate to it:
|
|
1307
|
+
|
|
1308
|
+
```ts
|
|
1309
|
+
import { createAgentSessionAdapter, run } from "@bastani/atomic/workflows";
|
|
1310
|
+
|
|
1311
|
+
// Default: in-process stage sessions with in-memory session managers.
|
|
1312
|
+
await run(helloWorld, { name: "Atomic" }, { cwd: process.cwd() });
|
|
1313
|
+
|
|
1314
|
+
// Shared options for every stage.
|
|
1315
|
+
await run(helloWorld, { name: "Atomic" }, {
|
|
1316
|
+
adapters: { agentSession: createAgentSessionAdapter({ agentDir: "/srv/atomic-agent" }) },
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
// Per-stage options on top of the default adapter.
|
|
1320
|
+
const stageSessions = createAgentSessionAdapter();
|
|
1321
|
+
await run(helloWorld, { name: "Atomic" }, {
|
|
1322
|
+
adapters: {
|
|
1323
|
+
agentSession: {
|
|
1324
|
+
create: (options, meta) =>
|
|
1325
|
+
stageSessions.create(meta?.stageName === "review" ? { ...options, cwd: "/srv/review" } : options, meta),
|
|
1326
|
+
},
|
|
1327
|
+
},
|
|
1328
|
+
});
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
Without an attached workflow UI, a stage that calls `ask_user_question` waits for an answer until the run is aborted through `opts.signal`. For unattended runs, pass `executionMode: "non_interactive"`; stages then run without `ask_user_question`.
|
|
1332
|
+
|
|
1276
1333
|
### `RunOpts`
|
|
1277
1334
|
|
|
1278
1335
|
```typescript
|
|
@@ -1307,6 +1364,7 @@ interface RunOpts {
|
|
|
1307
1364
|
readonly onRunStart?: (snapshot: RunSnapshot) => void;
|
|
1308
1365
|
readonly onStageStart?: (runId: string, snapshot: StageSnapshot) => void;
|
|
1309
1366
|
readonly onStageEnd?: (runId: string, snapshot: StageSnapshot) => unknown;
|
|
1367
|
+
readonly onStageSessionEvent?: (runId: string, stageId: string, event: AgentSessionEvent) => void;
|
|
1310
1368
|
readonly onRunEnd?: (
|
|
1311
1369
|
runId: string,
|
|
1312
1370
|
status: RunStatus,
|
|
@@ -1319,6 +1377,8 @@ interface RunOpts {
|
|
|
1319
1377
|
|
|
1320
1378
|
Supplies runtime adapters, execution policy, persistence, MCP, cancellation, graph/store integration, continuation metadata, and lifecycle callbacks to `run(...)`. Every field is optional.
|
|
1321
1379
|
|
|
1380
|
+
`onStageSessionEvent` streams every agent-session event from each stage (for example `message_update`, `tool_execution_start`, and `tool_execution_end`), tagged with the stage's `runId` and `stageId`. `AgentSessionEvent` is exported by `@bastani/atomic`. The stream continues when a fallback model replaces a stage's session. Stages in nested `ctx.workflow(...)` runs report the child run's `runId`. Exceptions thrown by the listener, and rejections from an async listener, are ignored and do not affect the stage.
|
|
1381
|
+
|
|
1322
1382
|
The public authoring declaration intentionally excludes runtime-only executor fields such as `defaultSessionDir`, `gitWorktreeSetupCache`, `durableBackend`, `durableScope`, and `onStageSession`.
|
|
1323
1383
|
|
|
1324
1384
|
#### Choosing the durable backend
|
|
@@ -1341,11 +1401,10 @@ One process uses one workflow database: DBOS runs a single executor per process,
|
|
|
1341
1401
|
```ts
|
|
1342
1402
|
import { run, WorkflowDurabilityRequiredError } from "@bastani/atomic/workflows";
|
|
1343
1403
|
|
|
1344
|
-
await run(helloWorld, { name: "Atomic" }, {
|
|
1404
|
+
await run(helloWorld, { name: "Atomic" }, { durability: { mode: "memory" } });
|
|
1345
1405
|
|
|
1346
1406
|
try {
|
|
1347
1407
|
await run(helloWorld, { name: "Atomic" }, {
|
|
1348
|
-
adapters,
|
|
1349
1408
|
durability: { mode: "durable", systemDatabaseUrl: process.env.WORKFLOW_DATABASE_URL },
|
|
1350
1409
|
});
|
|
1351
1410
|
} catch (error) {
|
|
@@ -1451,12 +1510,23 @@ interface Store {
|
|
|
1451
1510
|
removeRun(runId: string): boolean;
|
|
1452
1511
|
recordNotice(notice: WorkflowNotice): void;
|
|
1453
1512
|
ackNotice(id: string): boolean;
|
|
1513
|
+
snapshot(): StoreSnapshot;
|
|
1514
|
+
graphSnapshot(): StoreSnapshot;
|
|
1515
|
+
subscribe(listener: (snapshot: StoreSnapshot) => void): () => void;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
interface StoreSnapshot {
|
|
1519
|
+
readonly runs: readonly RunSnapshot[];
|
|
1520
|
+
readonly notices: readonly WorkflowNotice[];
|
|
1521
|
+
readonly version: number;
|
|
1454
1522
|
}
|
|
1455
1523
|
```
|
|
1456
1524
|
|
|
1457
1525
|
`createStore()` returns an isolated workflow state store. `store` is the default singleton exported by the SDK authoring surface.
|
|
1458
1526
|
|
|
1459
|
-
|
|
1527
|
+
`snapshot()` returns a JSON copy of every run and notice; `version` increases on every store change. `graphSnapshot()` returns a cached, size-bounded copy for graph rendering that stays the same object until the store changes. `subscribe(listener)` calls `listener` with a fresh `snapshot()` after each change and returns an unsubscribe function.
|
|
1528
|
+
|
|
1529
|
+
Atomic's runtime store also has prompt, session, and pause/resume methods used by embedded integrations; those controls are not part of the published `Store` contract shown here.
|
|
1460
1530
|
|
|
1461
1531
|
### `createCancellationRegistry()` / `cancellationRegistry`
|
|
1462
1532
|
|
|
@@ -716,12 +716,16 @@ A `verified` result is cached durably, so a resumed run does not click again. A
|
|
|
716
716
|
|
|
717
717
|
Workflow stages run chat language models. For a schema-backed decision, use the stage's `schema` and `structured_output` tool. It resolves an exact chat or registered classifier ID through the stage model registry and falls back on a classifier runtime failure. The direct `@bastani/pi-ai` examples below are for cases that need the classifier's probabilities or image generation from workflow TypeScript inside `ctx.tool(name, args, fn, { timeoutMs })`. Those calls are durable, replayable nodes. `model: "auto"` selects only chat language models for stage execution; `routerModel` may select a classifier for routing, but a classifier never executes a chat stage.
|
|
718
718
|
|
|
719
|
-
|
|
719
|
+
Atomic provides `@bastani/atomic/workflows`, `typebox`, and the exact imports `@bastani/atomic`, `@bastani/pi-ai`, and `@bastani/pi-ai/providers/all` to workflow files. The latter three use the running CLI's module instances and version, including `AuthStorage`. These examples need no project-local package installation, in npm or standalone-binary installs. A project copy does not override these host-provided imports.
|
|
720
|
+
|
|
721
|
+
For editor type checking, install the packages as dev dependencies at the version `atomic --version` prints. Atomic still runs these imports from the CLI, not from the project copy:
|
|
720
722
|
|
|
721
723
|
```sh
|
|
722
724
|
npm install --save-dev @bastani/atomic@"$(atomic --version)" @bastani/pi-ai@"$(atomic --version)"
|
|
723
725
|
```
|
|
724
726
|
|
|
727
|
+
Other imports, including unlisted deep package subpaths, still resolve from the owning project's `node_modules`. Install those dependencies next to the workflow. `/workflow reload` reloads workflow files and their relative imports, but does not replace the shared host modules; restart Atomic to use a different host version.
|
|
728
|
+
|
|
725
729
|
Pass `AuthStorage.create()` to `builtinModels()` so classifier and image requests use credentials saved by Atomic's `/login` flows in `auth.json`; this is the recommended default for workflows running inside Atomic. Ambient provider environment variables such as `TYPESAFE_API_KEY` and `OPENROUTER_API_KEY` continue to work. Never read, print, or pass credential values in workflow state, prompts, or artifacts. Both operations resolve instead of throwing: check `stopReason === "stop"` before using a result, and forward the tool's `signal` so a quit or targeted abort cancels the request.
|
|
726
730
|
|
|
727
731
|
#### Classify a request before choosing a stage
|
|
@@ -777,7 +781,7 @@ export default workflow({
|
|
|
777
781
|
});
|
|
778
782
|
```
|
|
779
783
|
|
|
780
|
-
The cached `{ choice, confidence }` is what a resumed run replays; the classifier is not asked again. For a consequential action, add policy checks or `ctx.ui.confirm(...)` before the side effect.
|
|
784
|
+
The cached `{ choice, confidence }` is what a resumed run replays; the classifier is not asked again. For a consequential action, add policy checks or `ctx.ui.confirm(...)` before the side effect. A schema-backed stage or task (`ctx.task(name, { schema, prompt })`, as the `classify-and-act` builtin does) can also make a validated decision. Its `structured_output` call can name a registered classifier or omit `model` and use the current chat model. A classifier probability is available only from a direct `classify()` call like the example above; a chat result does not supply one.
|
|
781
785
|
|
|
782
786
|
#### Generate an image artifact in a durable tool step
|
|
783
787
|
|