@m4ike1/ion-agent-core 0.1.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.
- package/README.md +622 -0
- package/dist/agent-loop.d.ts +24 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +560 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/agent.d.ts +122 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +422 -0
- package/dist/agent.js.map +1 -0
- package/dist/harness/agent-harness.d.ts +716 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +6 -0
- package/dist/harness/agent-harness.js.map +1 -0
- package/dist/harness/compaction/branch-summarization.d.ts +68 -0
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/harness/compaction/branch-summarization.js +176 -0
- package/dist/harness/compaction/branch-summarization.js.map +1 -0
- package/dist/harness/compaction/compaction.d.ts +126 -0
- package/dist/harness/compaction/compaction.d.ts.map +1 -0
- package/dist/harness/compaction/compaction.js +560 -0
- package/dist/harness/compaction/compaction.js.map +1 -0
- package/dist/harness/compaction/utils.d.ts +25 -0
- package/dist/harness/compaction/utils.d.ts.map +1 -0
- package/dist/harness/compaction/utils.js +120 -0
- package/dist/harness/compaction/utils.js.map +1 -0
- package/dist/harness/config.d.ts +9 -0
- package/dist/harness/config.d.ts.map +1 -0
- package/dist/harness/config.js +35 -0
- package/dist/harness/config.js.map +1 -0
- package/dist/harness/context.d.ts +9 -0
- package/dist/harness/context.d.ts.map +1 -0
- package/dist/harness/context.js +13 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/harness/env/nodejs.d.ts +43 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +865 -0
- package/dist/harness/env/nodejs.js.map +1 -0
- package/dist/harness/events.d.ts +25 -0
- package/dist/harness/events.d.ts.map +1 -0
- package/dist/harness/events.js +250 -0
- package/dist/harness/events.js.map +1 -0
- package/dist/harness/execution/assistant.d.ts +42 -0
- package/dist/harness/execution/assistant.d.ts.map +1 -0
- package/dist/harness/execution/assistant.js +82 -0
- package/dist/harness/execution/assistant.js.map +1 -0
- package/dist/harness/execution/effect-gate.d.ts +22 -0
- package/dist/harness/execution/effect-gate.d.ts.map +1 -0
- package/dist/harness/execution/effect-gate.js +49 -0
- package/dist/harness/execution/effect-gate.js.map +1 -0
- package/dist/harness/execution/tools.d.ts +67 -0
- package/dist/harness/execution/tools.d.ts.map +1 -0
- package/dist/harness/execution/tools.js +121 -0
- package/dist/harness/execution/tools.js.map +1 -0
- package/dist/harness/hooks.d.ts +38 -0
- package/dist/harness/hooks.d.ts.map +1 -0
- package/dist/harness/hooks.js +406 -0
- package/dist/harness/hooks.js.map +1 -0
- package/dist/harness/messages.d.ts +51 -0
- package/dist/harness/messages.d.ts.map +1 -0
- package/dist/harness/messages.js +102 -0
- package/dist/harness/messages.js.map +1 -0
- package/dist/harness/prompt-templates.d.ts +49 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +225 -0
- package/dist/harness/prompt-templates.js.map +1 -0
- package/dist/harness/result.d.ts +133 -0
- package/dist/harness/result.d.ts.map +1 -0
- package/dist/harness/result.js +81 -0
- package/dist/harness/result.js.map +1 -0
- package/dist/harness/runtime/drive/boundary.d.ts +28 -0
- package/dist/harness/runtime/drive/boundary.d.ts.map +1 -0
- package/dist/harness/runtime/drive/boundary.js +173 -0
- package/dist/harness/runtime/drive/boundary.js.map +1 -0
- package/dist/harness/runtime/drive/checkpoint.d.ts +8 -0
- package/dist/harness/runtime/drive/checkpoint.d.ts.map +1 -0
- package/dist/harness/runtime/drive/checkpoint.js +133 -0
- package/dist/harness/runtime/drive/checkpoint.js.map +1 -0
- package/dist/harness/runtime/drive/deferred.d.ts +15 -0
- package/dist/harness/runtime/drive/deferred.d.ts.map +1 -0
- package/dist/harness/runtime/drive/deferred.js +179 -0
- package/dist/harness/runtime/drive/deferred.js.map +1 -0
- package/dist/harness/runtime/drive/generation.d.ts +8 -0
- package/dist/harness/runtime/drive/generation.d.ts.map +1 -0
- package/dist/harness/runtime/drive/generation.js +205 -0
- package/dist/harness/runtime/drive/generation.js.map +1 -0
- package/dist/harness/runtime/drive/reconcile.d.ts +5 -0
- package/dist/harness/runtime/drive/reconcile.d.ts.map +1 -0
- package/dist/harness/runtime/drive/reconcile.js +140 -0
- package/dist/harness/runtime/drive/reconcile.js.map +1 -0
- package/dist/harness/runtime/drive/recovery.d.ts +8 -0
- package/dist/harness/runtime/drive/recovery.d.ts.map +1 -0
- package/dist/harness/runtime/drive/recovery.js +85 -0
- package/dist/harness/runtime/drive/recovery.js.map +1 -0
- package/dist/harness/runtime/drive/response.d.ts +23 -0
- package/dist/harness/runtime/drive/response.d.ts.map +1 -0
- package/dist/harness/runtime/drive/response.js +394 -0
- package/dist/harness/runtime/drive/response.js.map +1 -0
- package/dist/harness/runtime/drive/retry.d.ts +4 -0
- package/dist/harness/runtime/drive/retry.d.ts.map +1 -0
- package/dist/harness/runtime/drive/retry.js +34 -0
- package/dist/harness/runtime/drive/retry.js.map +1 -0
- package/dist/harness/runtime/drive/structural.d.ts +32 -0
- package/dist/harness/runtime/drive/structural.d.ts.map +1 -0
- package/dist/harness/runtime/drive/structural.js +929 -0
- package/dist/harness/runtime/drive/structural.js.map +1 -0
- package/dist/harness/runtime/drive/terminal.d.ts +7 -0
- package/dist/harness/runtime/drive/terminal.d.ts.map +1 -0
- package/dist/harness/runtime/drive/terminal.js +49 -0
- package/dist/harness/runtime/drive/terminal.js.map +1 -0
- package/dist/harness/runtime/drive/tool-placement.d.ts +14 -0
- package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -0
- package/dist/harness/runtime/drive/tool-placement.js +244 -0
- package/dist/harness/runtime/drive/tool-placement.js.map +1 -0
- package/dist/harness/runtime/drive/tools.d.ts +6 -0
- package/dist/harness/runtime/drive/tools.d.ts.map +1 -0
- package/dist/harness/runtime/drive/tools.js +449 -0
- package/dist/harness/runtime/drive/tools.js.map +1 -0
- package/dist/harness/runtime/drive.d.ts +6 -0
- package/dist/harness/runtime/drive.d.ts.map +1 -0
- package/dist/harness/runtime/drive.js +93 -0
- package/dist/harness/runtime/drive.js.map +1 -0
- package/dist/harness/runtime/harness.d.ts +59 -0
- package/dist/harness/runtime/harness.d.ts.map +1 -0
- package/dist/harness/runtime/harness.js +311 -0
- package/dist/harness/runtime/harness.js.map +1 -0
- package/dist/harness/runtime/index.d.ts +2 -0
- package/dist/harness/runtime/index.d.ts.map +1 -0
- package/dist/harness/runtime/index.js +2 -0
- package/dist/harness/runtime/index.js.map +1 -0
- package/dist/harness/runtime/lane.d.ts +101 -0
- package/dist/harness/runtime/lane.d.ts.map +1 -0
- package/dist/harness/runtime/lane.js +1575 -0
- package/dist/harness/runtime/lane.js.map +1 -0
- package/dist/harness/runtime/progress.d.ts +15 -0
- package/dist/harness/runtime/progress.d.ts.map +1 -0
- package/dist/harness/runtime/progress.js +66 -0
- package/dist/harness/runtime/progress.js.map +1 -0
- package/dist/harness/runtime/reducer.d.ts +5 -0
- package/dist/harness/runtime/reducer.d.ts.map +1 -0
- package/dist/harness/runtime/reducer.js +240 -0
- package/dist/harness/runtime/reducer.js.map +1 -0
- package/dist/harness/runtime/restore.d.ts +24 -0
- package/dist/harness/runtime/restore.d.ts.map +1 -0
- package/dist/harness/runtime/restore.js +117 -0
- package/dist/harness/runtime/restore.js.map +1 -0
- package/dist/harness/runtime/transcript.d.ts +21 -0
- package/dist/harness/runtime/transcript.d.ts.map +1 -0
- package/dist/harness/runtime/transcript.js +72 -0
- package/dist/harness/runtime/transcript.js.map +1 -0
- package/dist/harness/runtime/types.d.ts +105 -0
- package/dist/harness/runtime/types.d.ts.map +1 -0
- package/dist/harness/runtime/types.js +61 -0
- package/dist/harness/runtime/types.js.map +1 -0
- package/dist/harness/session/commit.d.ts +53 -0
- package/dist/harness/session/commit.d.ts.map +1 -0
- package/dist/harness/session/commit.js +57 -0
- package/dist/harness/session/commit.js.map +1 -0
- package/dist/harness/session/context.d.ts +10 -0
- package/dist/harness/session/context.d.ts.map +1 -0
- package/dist/harness/session/context.js +49 -0
- package/dist/harness/session/context.js.map +1 -0
- package/dist/harness/session/fork-policy.d.ts +21 -0
- package/dist/harness/session/fork-policy.d.ts.map +1 -0
- package/dist/harness/session/fork-policy.js +55 -0
- package/dist/harness/session/fork-policy.js.map +1 -0
- package/dist/harness/session/fork.d.ts +16 -0
- package/dist/harness/session/fork.d.ts.map +1 -0
- package/dist/harness/session/fork.js +79 -0
- package/dist/harness/session/fork.js.map +1 -0
- package/dist/harness/session/in-memory-storage-state.d.ts +39 -0
- package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -0
- package/dist/harness/session/in-memory-storage-state.js +276 -0
- package/dist/harness/session/in-memory-storage-state.js.map +1 -0
- package/dist/harness/session/index.d.ts +11 -0
- package/dist/harness/session/index.d.ts.map +1 -0
- package/dist/harness/session/index.js +9 -0
- package/dist/harness/session/index.js.map +1 -0
- package/dist/harness/session/jsonl/codec.d.ts +21 -0
- package/dist/harness/session/jsonl/codec.d.ts.map +1 -0
- package/dist/harness/session/jsonl/codec.js +45 -0
- package/dist/harness/session/jsonl/codec.js.map +1 -0
- package/dist/harness/session/jsonl/fork.d.ts +38 -0
- package/dist/harness/session/jsonl/fork.d.ts.map +1 -0
- package/dist/harness/session/jsonl/fork.js +264 -0
- package/dist/harness/session/jsonl/fork.js.map +1 -0
- package/dist/harness/session/jsonl/index.d.ts +4 -0
- package/dist/harness/session/jsonl/index.d.ts.map +1 -0
- package/dist/harness/session/jsonl/index.js +4 -0
- package/dist/harness/session/jsonl/index.js.map +1 -0
- package/dist/harness/session/jsonl/io.d.ts +14 -0
- package/dist/harness/session/jsonl/io.d.ts.map +1 -0
- package/dist/harness/session/jsonl/io.js +86 -0
- package/dist/harness/session/jsonl/io.js.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts +43 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.js +462 -0
- package/dist/harness/session/jsonl/legacy-v3.js.map +1 -0
- package/dist/harness/session/jsonl/repo.d.ts +35 -0
- package/dist/harness/session/jsonl/repo.d.ts.map +1 -0
- package/dist/harness/session/jsonl/repo.js +301 -0
- package/dist/harness/session/jsonl/repo.js.map +1 -0
- package/dist/harness/session/jsonl/storage.d.ts +40 -0
- package/dist/harness/session/jsonl/storage.d.ts.map +1 -0
- package/dist/harness/session/jsonl/storage.js +200 -0
- package/dist/harness/session/jsonl/storage.js.map +1 -0
- package/dist/harness/session/jsonl/types.d.ts +39 -0
- package/dist/harness/session/jsonl/types.d.ts.map +1 -0
- package/dist/harness/session/jsonl/types.js +3 -0
- package/dist/harness/session/jsonl/types.js.map +1 -0
- package/dist/harness/session/memory.d.ts +48 -0
- package/dist/harness/session/memory.d.ts.map +1 -0
- package/dist/harness/session/memory.js +369 -0
- package/dist/harness/session/memory.js.map +1 -0
- package/dist/harness/session/mutation-line.d.ts +8 -0
- package/dist/harness/session/mutation-line.d.ts.map +1 -0
- package/dist/harness/session/mutation-line.js +21 -0
- package/dist/harness/session/mutation-line.js.map +1 -0
- package/dist/harness/session/session.d.ts +82 -0
- package/dist/harness/session/session.d.ts.map +1 -0
- package/dist/harness/session/session.js +361 -0
- package/dist/harness/session/session.js.map +1 -0
- package/dist/harness/session/testing/benchmark/datasets.d.ts +12 -0
- package/dist/harness/session/testing/benchmark/datasets.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/datasets.js +24 -0
- package/dist/harness/session/testing/benchmark/datasets.js.map +1 -0
- package/dist/harness/session/testing/benchmark/session-repo.d.ts +42 -0
- package/dist/harness/session/testing/benchmark/session-repo.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/session-repo.js +127 -0
- package/dist/harness/session/testing/benchmark/session-repo.js.map +1 -0
- package/dist/harness/session/testing/benchmark/storage.d.ts +25 -0
- package/dist/harness/session/testing/benchmark/storage.d.ts.map +1 -0
- package/dist/harness/session/testing/benchmark/storage.js +114 -0
- package/dist/harness/session/testing/benchmark/storage.js.map +1 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts +23 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance/session-repo.js +809 -0
- package/dist/harness/session/testing/conformance/session-repo.js.map +1 -0
- package/dist/harness/session/testing/conformance/storage.d.ts +4 -0
- package/dist/harness/session/testing/conformance/storage.d.ts.map +1 -0
- package/dist/harness/session/testing/conformance/storage.js +600 -0
- package/dist/harness/session/testing/conformance/storage.js.map +1 -0
- package/dist/harness/session/testing/gating-storage.d.ts +26 -0
- package/dist/harness/session/testing/gating-storage.d.ts.map +1 -0
- package/dist/harness/session/testing/gating-storage.js +100 -0
- package/dist/harness/session/testing/gating-storage.js.map +1 -0
- package/dist/harness/session/testing/index.d.ts +10 -0
- package/dist/harness/session/testing/index.d.ts.map +1 -0
- package/dist/harness/session/testing/index.js +9 -0
- package/dist/harness/session/testing/index.js.map +1 -0
- package/dist/harness/session/testing/instrumented-storage.d.ts +11 -0
- package/dist/harness/session/testing/instrumented-storage.d.ts.map +1 -0
- package/dist/harness/session/testing/instrumented-storage.js +16 -0
- package/dist/harness/session/testing/instrumented-storage.js.map +1 -0
- package/dist/harness/session/testing/storage-decorator.d.ts +20 -0
- package/dist/harness/session/testing/storage-decorator.d.ts.map +1 -0
- package/dist/harness/session/testing/storage-decorator.js +41 -0
- package/dist/harness/session/testing/storage-decorator.js.map +1 -0
- package/dist/harness/session/testing/types.d.ts +12 -0
- package/dist/harness/session/testing/types.d.ts.map +1 -0
- package/dist/harness/session/testing/types.js +2 -0
- package/dist/harness/session/testing/types.js.map +1 -0
- package/dist/harness/session/types.d.ts +512 -0
- package/dist/harness/session/types.d.ts.map +1 -0
- package/dist/harness/session/types.js +9 -0
- package/dist/harness/session/types.js.map +1 -0
- package/dist/harness/session/values.d.ts +95 -0
- package/dist/harness/session/values.d.ts.map +1 -0
- package/dist/harness/session/values.js +81 -0
- package/dist/harness/session/values.js.map +1 -0
- package/dist/harness/skills.d.ts +46 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +326 -0
- package/dist/harness/skills.js.map +1 -0
- package/dist/harness/system-prompt.d.ts +3 -0
- package/dist/harness/system-prompt.d.ts.map +1 -0
- package/dist/harness/system-prompt.js +30 -0
- package/dist/harness/system-prompt.js.map +1 -0
- package/dist/harness/telemetry.d.ts +1242 -0
- package/dist/harness/telemetry.d.ts.map +1 -0
- package/dist/harness/telemetry.js +527 -0
- package/dist/harness/telemetry.js.map +1 -0
- package/dist/harness/tools/bash.d.ts +27 -0
- package/dist/harness/tools/bash.d.ts.map +1 -0
- package/dist/harness/tools/bash.js +105 -0
- package/dist/harness/tools/bash.js.map +1 -0
- package/dist/harness/tools/edit-diff.d.ts +89 -0
- package/dist/harness/tools/edit-diff.d.ts.map +1 -0
- package/dist/harness/tools/edit-diff.js +386 -0
- package/dist/harness/tools/edit-diff.js.map +1 -0
- package/dist/harness/tools/edit.d.ts +19 -0
- package/dist/harness/tools/edit.d.ts.map +1 -0
- package/dist/harness/tools/edit.js +108 -0
- package/dist/harness/tools/edit.js.map +1 -0
- package/dist/harness/tools/file-mutation-queue.d.ts +5 -0
- package/dist/harness/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/harness/tools/file-mutation-queue.js +46 -0
- package/dist/harness/tools/file-mutation-queue.js.map +1 -0
- package/dist/harness/tools/image.d.ts +3 -0
- package/dist/harness/tools/image.d.ts.map +1 -0
- package/dist/harness/tools/image.js +106 -0
- package/dist/harness/tools/image.js.map +1 -0
- package/dist/harness/tools/index.d.ts +6 -0
- package/dist/harness/tools/index.d.ts.map +1 -0
- package/dist/harness/tools/index.js +5 -0
- package/dist/harness/tools/index.js.map +1 -0
- package/dist/harness/tools/path-utils.d.ts +5 -0
- package/dist/harness/tools/path-utils.d.ts.map +1 -0
- package/dist/harness/tools/path-utils.js +26 -0
- package/dist/harness/tools/path-utils.js.map +1 -0
- package/dist/harness/tools/read.d.ts +35 -0
- package/dist/harness/tools/read.d.ts.map +1 -0
- package/dist/harness/tools/read.js +108 -0
- package/dist/harness/tools/read.js.map +1 -0
- package/dist/harness/tools/tool-context.d.ts +6 -0
- package/dist/harness/tools/tool-context.d.ts.map +1 -0
- package/dist/harness/tools/tool-context.js +2 -0
- package/dist/harness/tools/tool-context.js.map +1 -0
- package/dist/harness/tools/write.d.ts +11 -0
- package/dist/harness/tools/write.d.ts.map +1 -0
- package/dist/harness/tools/write.js +31 -0
- package/dist/harness/tools/write.js.map +1 -0
- package/dist/harness/types.d.ts +305 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +75 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/utils/adaptive-publisher.d.ts +24 -0
- package/dist/harness/utils/adaptive-publisher.d.ts.map +1 -0
- package/dist/harness/utils/adaptive-publisher.js +79 -0
- package/dist/harness/utils/adaptive-publisher.js.map +1 -0
- package/dist/harness/utils/output-capture.d.ts +31 -0
- package/dist/harness/utils/output-capture.d.ts.map +1 -0
- package/dist/harness/utils/output-capture.js +217 -0
- package/dist/harness/utils/output-capture.js.map +1 -0
- package/dist/harness/utils/shell-output.d.ts +29 -0
- package/dist/harness/utils/shell-output.d.ts.map +1 -0
- package/dist/harness/utils/shell-output.js +71 -0
- package/dist/harness/utils/shell-output.js.map +1 -0
- package/dist/harness/utils/truncate.d.ts +71 -0
- package/dist/harness/utils/truncate.d.ts.map +1 -0
- package/dist/harness/utils/truncate.js +296 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/harness/utils/usage.d.ts +4 -0
- package/dist/harness/utils/usage.d.ts.map +1 -0
- package/dist/harness/utils/usage.js +33 -0
- package/dist/harness/utils/usage.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/node.js.map +1 -0
- package/dist/proxy.d.ts +72 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +310 -0
- package/dist/proxy.js.map +1 -0
- package/dist/search/index.d.ts +29 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +2 -0
- package/dist/search/index.js.map +1 -0
- package/dist/stream-fn.d.ts +10 -0
- package/dist/stream-fn.d.ts.map +1 -0
- package/dist/stream-fn.js +17 -0
- package/dist/stream-fn.js.map +1 -0
- package/dist/types.d.ts +416 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +89 -0
package/README.md
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
# @m4ike1/ion-agent-core
|
|
2
|
+
|
|
3
|
+
Stateful agent runtime with tool execution and event streaming. Built on `@m4ike1/ion-ai`.
|
|
4
|
+
|
|
5
|
+
The `Agent` class owns the transcript, runs the prompt → LLM → tool loop, and emits
|
|
6
|
+
lifecycle events for UI updates. For details see
|
|
7
|
+
[docs/how-to.md](docs/how-to.md) (recipes) and
|
|
8
|
+
[docs/api-reference.md](docs/api-reference.md) (API dictionary).
|
|
9
|
+
The durable conversation/session harness in this package is specified separately in
|
|
10
|
+
[docs/harness.md](docs/harness.md).
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @m4ike1/ion-agent-core
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Entrypoints
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Agent } from "@m4ike1/ion-agent-core"; // core runtime (Agent, agentLoop, streamProxy, types)
|
|
22
|
+
import { NodeExecutionEnv } from "@m4ike1/ion-agent-core/node"; // Node execution env + everything above
|
|
23
|
+
import type { ... } from "@m4ike1/ion-agent-core/harness/session"; // session layer
|
|
24
|
+
import type { ... } from "@m4ike1/ion-agent-core/harness/session/testing"; // session test helpers
|
|
25
|
+
import { reduceLaneSnapshot } from "@m4ike1/ion-agent-core/harness/runtime/reducer";
|
|
26
|
+
import type { ... } from "@m4ike1/ion-agent-core/harness/context";
|
|
27
|
+
import { NodeExecutionEnv } from "@m4ike1/ion-agent-core/harness/env/nodejs";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### SQLite session backends
|
|
31
|
+
|
|
32
|
+
The SQLite session backend and the `node:sqlite` adapter live in a separate package,
|
|
33
|
+
`@m4ike1/ion-session-backend-sqlite-node`, so the core package does not pull in
|
|
34
|
+
runtime builtins or native SQLite dependencies by default. The backend accepts a
|
|
35
|
+
runtime-specific SQLite factory, allowing other session backends to ship as their
|
|
36
|
+
own packages in the future.
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { Agent } from "@m4ike1/ion-agent-core";
|
|
42
|
+
import { createModels } from "@m4ike1/ion-ai";
|
|
43
|
+
import { anthropicProvider } from "@m4ike1/ion-ai/providers/anthropic";
|
|
44
|
+
|
|
45
|
+
const models = createModels();
|
|
46
|
+
models.setProvider(anthropicProvider());
|
|
47
|
+
const model = models.getModel("anthropic", "claude-sonnet-4-6");
|
|
48
|
+
if (!model) throw new Error("Model not found");
|
|
49
|
+
|
|
50
|
+
const agent = new Agent({
|
|
51
|
+
initialState: {
|
|
52
|
+
systemPrompt: "You are a helpful assistant.",
|
|
53
|
+
model,
|
|
54
|
+
},
|
|
55
|
+
streamFn: models.streamSimple.bind(models),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
agent.subscribe((event) => {
|
|
59
|
+
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
60
|
+
// Stream just the new text chunk
|
|
61
|
+
process.stdout.write(event.assistantMessageEvent.delta);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
await agent.prompt("Hello!");
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`streamFn` defaults to the function installed via `setDefaultStreamFn()` when omitted.
|
|
69
|
+
`Models.streamSimple` satisfies the `StreamFn` contract: never throw for
|
|
70
|
+
request/model/runtime failures; encode failures in the returned stream as a final
|
|
71
|
+
`AssistantMessage` with `stopReason` `"error"` / `"aborted"`.
|
|
72
|
+
|
|
73
|
+
## Experimental facet services
|
|
74
|
+
|
|
75
|
+
Transport-neutral facet-service primitives live in `@m4ike1/chord`. The agent core
|
|
76
|
+
does not export the service runtime.
|
|
77
|
+
|
|
78
|
+
## Core Concepts
|
|
79
|
+
|
|
80
|
+
### AgentMessage vs LLM Message
|
|
81
|
+
|
|
82
|
+
The agent works with `AgentMessage`, a flexible type that can include:
|
|
83
|
+
|
|
84
|
+
- Standard LLM messages (`user`, `assistant`, `toolResult`)
|
|
85
|
+
- Custom app-specific message types via declaration merging
|
|
86
|
+
|
|
87
|
+
LLMs only understand `user`, `assistant`, and `toolResult`. The `convertToLlm`
|
|
88
|
+
function bridges this gap by filtering and transforming messages before each LLM call.
|
|
89
|
+
The default `convertToLlm` passes through only `user` / `assistant` / `toolResult`.
|
|
90
|
+
|
|
91
|
+
### Message Flow
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
AgentMessage[] → transformContext() → AgentMessage[] → convertToLlm() → Message[] → LLM
|
|
95
|
+
(optional) (required)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
1. **transformContext**: Prune old messages, inject external context
|
|
99
|
+
2. **convertToLlm**: Filter out UI-only messages, convert custom types to LLM format
|
|
100
|
+
|
|
101
|
+
Both hooks must not throw or reject; return a safe fallback instead. A throwing
|
|
102
|
+
`convertToLlm`, `shouldStopAfterTurn`, or `prepareNextTurn` interrupts the low-level
|
|
103
|
+
loop without producing a normal event sequence.
|
|
104
|
+
|
|
105
|
+
## Event Flow
|
|
106
|
+
|
|
107
|
+
The agent emits events for UI updates. Understanding the event sequence helps build
|
|
108
|
+
responsive interfaces.
|
|
109
|
+
|
|
110
|
+
### prompt() Event Sequence
|
|
111
|
+
|
|
112
|
+
When you call `prompt("Hello")`:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
prompt("Hello")
|
|
116
|
+
├─ agent_start
|
|
117
|
+
├─ turn_start
|
|
118
|
+
├─ message_start { message: userMessage } // Your prompt
|
|
119
|
+
├─ message_end { message: userMessage }
|
|
120
|
+
├─ message_start { message: assistantMessage } // LLM starts responding
|
|
121
|
+
├─ message_update { message: partial... } // Streaming chunks
|
|
122
|
+
├─ message_update { message: partial... }
|
|
123
|
+
├─ message_end { message: assistantMessage } // Complete response
|
|
124
|
+
├─ turn_end { message, toolResults: [] }
|
|
125
|
+
└─ agent_end { messages: [...] }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### With Tool Calls
|
|
129
|
+
|
|
130
|
+
If the assistant calls tools, the loop continues:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
prompt("Read config.json")
|
|
134
|
+
├─ agent_start
|
|
135
|
+
├─ turn_start
|
|
136
|
+
├─ message_start/end { userMessage }
|
|
137
|
+
├─ message_start { assistantMessage with toolCall }
|
|
138
|
+
├─ message_update...
|
|
139
|
+
├─ message_end { assistantMessage }
|
|
140
|
+
├─ tool_execution_start { toolCallId, toolName, args }
|
|
141
|
+
├─ tool_execution_update { partialResult } // If tool streams
|
|
142
|
+
├─ tool_execution_end { toolCallId, result }
|
|
143
|
+
├─ message_start/end { toolResultMessage }
|
|
144
|
+
├─ turn_end { message, toolResults: [toolResult] }
|
|
145
|
+
│
|
|
146
|
+
├─ turn_start // Next turn
|
|
147
|
+
├─ message_start { assistantMessage } // LLM responds to tool result
|
|
148
|
+
├─ message_update...
|
|
149
|
+
├─ message_end
|
|
150
|
+
├─ turn_end
|
|
151
|
+
└─ agent_end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Tool execution mode is configurable:
|
|
155
|
+
|
|
156
|
+
- `parallel` (default): preflight tool calls sequentially, execute allowed tools
|
|
157
|
+
concurrently, emit `tool_execution_end` as soon as each tool is finalized, then
|
|
158
|
+
emit toolResult messages and `turn_end.toolResults` in assistant source order
|
|
159
|
+
- `sequential`: execute tool calls one by one, matching the historical behavior
|
|
160
|
+
|
|
161
|
+
In parallel mode, tool completion events follow tool completion order, but persisted
|
|
162
|
+
toolResult messages still follow assistant source order.
|
|
163
|
+
|
|
164
|
+
The mode can be set globally via `toolExecution` in the agent config, or per-tool
|
|
165
|
+
via `executionMode` on `AgentTool`. If any tool call in a batch targets a tool with
|
|
166
|
+
`executionMode: "sequential"`, the entire batch executes sequentially regardless of
|
|
167
|
+
the global setting.
|
|
168
|
+
|
|
169
|
+
The `beforeToolCall` hook runs after `tool_execution_start` and validated argument
|
|
170
|
+
parsing. It can block execution and attach `terminate: true` to the blocked result.
|
|
171
|
+
The `afterToolCall` hook runs after tool execution finishes and before
|
|
172
|
+
`tool_execution_end` and final tool result message events are emitted.
|
|
173
|
+
|
|
174
|
+
Tools, blocked `beforeToolCall` results, and `afterToolCall` overrides can return
|
|
175
|
+
`terminate: true` to hint that the automatic follow-up LLM call should be skipped.
|
|
176
|
+
The loop only stops early when every finalized tool result in that batch sets
|
|
177
|
+
`terminate: true`. Mixed batches continue normally.
|
|
178
|
+
|
|
179
|
+
When you use the `Agent` class, assistant `message_end` processing is treated as a
|
|
180
|
+
barrier before tool preflight begins. That means `beforeToolCall` sees agent state
|
|
181
|
+
that already includes the assistant message that requested the tool call.
|
|
182
|
+
|
|
183
|
+
### Truncated responses
|
|
184
|
+
|
|
185
|
+
If the assistant message ends with `stopReason: "length"` (output token limit hit),
|
|
186
|
+
no tool call in that message is executed: streamed tool-call arguments may be
|
|
187
|
+
silently truncated, so each call is reported as an error result and the model is
|
|
188
|
+
asked to re-issue the calls with complete arguments.
|
|
189
|
+
|
|
190
|
+
### continue() Event Sequence
|
|
191
|
+
|
|
192
|
+
`continue()` resumes from existing context without adding a new message. Use it for
|
|
193
|
+
retries after errors.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// After an error, retry from current state
|
|
197
|
+
await agent.continue();
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The last message in context must be `user` or `toolResult` (not `assistant`).
|
|
201
|
+
If the last message is `assistant` but a steering or follow-up message is queued,
|
|
202
|
+
`continue()` runs the queued message instead of throwing.
|
|
203
|
+
|
|
204
|
+
### Event Types
|
|
205
|
+
|
|
206
|
+
| Event | Description |
|
|
207
|
+
|-------|-------------|
|
|
208
|
+
| `agent_start` | Agent begins processing |
|
|
209
|
+
| `agent_end` | Final event for the run. Awaited subscribers for this event still count toward settlement |
|
|
210
|
+
| `turn_start` | New turn begins (one LLM call + tool executions) |
|
|
211
|
+
| `turn_end` | Turn completes with assistant message and tool results |
|
|
212
|
+
| `message_start` | Any message begins (user, assistant, toolResult) |
|
|
213
|
+
| `message_update` | **Assistant only.** Includes `assistantMessageEvent` with delta |
|
|
214
|
+
| `message_end` | Message completes |
|
|
215
|
+
| `tool_execution_start` | Tool begins |
|
|
216
|
+
| `tool_execution_update` | Tool streams progress |
|
|
217
|
+
| `tool_execution_end` | Tool completes |
|
|
218
|
+
|
|
219
|
+
`Agent.subscribe()` listeners are awaited in registration order and receive the
|
|
220
|
+
active run's `AbortSignal`. `agent_end` means no more loop events will be emitted,
|
|
221
|
+
but `await agent.waitForIdle()` and `await agent.prompt(...)` only settle after
|
|
222
|
+
awaited `agent_end` listeners finish. Raw `agentLoop()` / `agentLoopContinue()`
|
|
223
|
+
streams are observational only: they preserve event order but do not wait for your
|
|
224
|
+
async event handling to settle before later producer phases continue.
|
|
225
|
+
|
|
226
|
+
## Agent Options
|
|
227
|
+
|
|
228
|
+
See [docs/api-reference.md](docs/api-reference.md) for the full field dictionary.
|
|
229
|
+
Common setup:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const agent = new Agent({
|
|
233
|
+
// Initial state
|
|
234
|
+
initialState: {
|
|
235
|
+
systemPrompt: "You are a helpful assistant.",
|
|
236
|
+
model,
|
|
237
|
+
thinkingLevel: "medium", // "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
|
|
238
|
+
tools: [readFileTool],
|
|
239
|
+
messages: [],
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
// Convert AgentMessage[] to LLM Message[] (required for custom message types)
|
|
243
|
+
convertToLlm: (messages) => messages.filter(...),
|
|
244
|
+
|
|
245
|
+
// Transform context before convertToLlm (for pruning, compaction)
|
|
246
|
+
transformContext: async (messages, signal) => pruneOldMessages(messages),
|
|
247
|
+
|
|
248
|
+
// Steering mode: "one-at-a-time" (default) or "all"
|
|
249
|
+
steeringMode: "one-at-a-time",
|
|
250
|
+
|
|
251
|
+
// Follow-up mode: "one-at-a-time" (default) or "all"
|
|
252
|
+
followUpMode: "one-at-a-time",
|
|
253
|
+
|
|
254
|
+
// Stream function (falls back to setDefaultStreamFn() when omitted)
|
|
255
|
+
streamFn: models.streamSimple.bind(models),
|
|
256
|
+
|
|
257
|
+
// Session ID for provider caching
|
|
258
|
+
sessionId: "session-123",
|
|
259
|
+
|
|
260
|
+
// Dynamic API key resolution (for expiring OAuth tokens)
|
|
261
|
+
getApiKey: async (provider) => refreshToken(),
|
|
262
|
+
|
|
263
|
+
// Tool execution mode: "parallel" (default) or "sequential"
|
|
264
|
+
toolExecution: "parallel",
|
|
265
|
+
|
|
266
|
+
// Preflight each tool call after args are validated. Can block execution.
|
|
267
|
+
beforeToolCall: async ({ toolCall, args, context }) => {
|
|
268
|
+
if (toolCall.name === "bash") {
|
|
269
|
+
return { block: true, reason: "bash is disabled", terminate: true };
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// Postprocess each tool result before final tool events are emitted.
|
|
274
|
+
afterToolCall: async ({ toolCall, result, isError, context }) => {
|
|
275
|
+
if (toolCall.name === "notify_done" && !isError) {
|
|
276
|
+
return { terminate: true };
|
|
277
|
+
}
|
|
278
|
+
if (!isError) {
|
|
279
|
+
return { details: { ...result.details, audited: true } };
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
// Stop gracefully after a completed turn, before queued messages are polled.
|
|
284
|
+
shouldStopAfterTurn: async ({ context }, signal) => {
|
|
285
|
+
return shouldCompactBeforeNextTurn(context.messages);
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
// Replace context/model/thinking state before the next turn starts.
|
|
289
|
+
prepareNextTurnWithContext: async ({ context, message }) => undefined,
|
|
290
|
+
|
|
291
|
+
// Custom thinking budgets for token-based providers
|
|
292
|
+
thinkingBudgets: {
|
|
293
|
+
minimal: 128,
|
|
294
|
+
low: 512,
|
|
295
|
+
medium: 1024,
|
|
296
|
+
high: 2048,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The `Agent` class accepts `shouldStopAfterTurn` in `AgentOptions`. Low-level loop
|
|
302
|
+
callers set the same hook in `AgentLoopConfig`. It runs after `turn_end` is emitted
|
|
303
|
+
and after the assistant response and any tool executions have completed normally.
|
|
304
|
+
If it returns `true`, the loop emits `agent_end` and exits before polling steering
|
|
305
|
+
or follow-up queues, and before starting another LLM call. It does not abort the
|
|
306
|
+
provider stream, does not cancel running tools, and does not alter the assistant
|
|
307
|
+
message stop reason. The `AgentOptions` callback also receives the active run's
|
|
308
|
+
`AbortSignal` as its second argument.
|
|
309
|
+
|
|
310
|
+
## Agent State
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
interface AgentState {
|
|
314
|
+
systemPrompt: string;
|
|
315
|
+
model: Model<any>;
|
|
316
|
+
thinkingLevel: ThinkingLevel;
|
|
317
|
+
tools: AgentTool<any>[];
|
|
318
|
+
messages: AgentMessage[];
|
|
319
|
+
readonly isStreaming: boolean;
|
|
320
|
+
readonly streamingMessage?: AgentMessage;
|
|
321
|
+
readonly pendingToolCalls: ReadonlySet<string>;
|
|
322
|
+
readonly errorMessage?: string;
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Access state via `agent.state`.
|
|
327
|
+
|
|
328
|
+
Assigning `agent.state.tools = [...]` or `agent.state.messages = [...]` copies the
|
|
329
|
+
top-level array before storing it. Mutating the returned array mutates the current
|
|
330
|
+
agent state.
|
|
331
|
+
|
|
332
|
+
During streaming, `agent.state.streamingMessage` contains the current partial
|
|
333
|
+
assistant message.
|
|
334
|
+
|
|
335
|
+
`agent.state.isStreaming` remains `true` until the run fully settles, including
|
|
336
|
+
awaited `agent_end` subscribers. `agent.state.errorMessage` holds the error message
|
|
337
|
+
from the most recent failed or aborted assistant turn, if any.
|
|
338
|
+
|
|
339
|
+
## Methods
|
|
340
|
+
|
|
341
|
+
### Prompting
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// Text prompt
|
|
345
|
+
await agent.prompt("Hello");
|
|
346
|
+
|
|
347
|
+
// With images
|
|
348
|
+
await agent.prompt("What's in this image?", [
|
|
349
|
+
{ type: "image", data: base64Data, mimeType: "image/jpeg" }
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
// AgentMessage directly
|
|
353
|
+
await agent.prompt({ role: "user", content: [{ type: "text", text: "Hello" }], timestamp: Date.now() });
|
|
354
|
+
|
|
355
|
+
// Batch of messages
|
|
356
|
+
await agent.prompt([msg1, msg2]);
|
|
357
|
+
|
|
358
|
+
// Continue from current context (last message must be user or toolResult)
|
|
359
|
+
await agent.continue();
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Only one run at a time: `prompt()` / `continue()` throw while a run is active. Use
|
|
363
|
+
`steer()` / `followUp()` to queue messages, or wait for completion. `reset()`
|
|
364
|
+
throws while a run is active.
|
|
365
|
+
|
|
366
|
+
### State Management
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
agent.state.systemPrompt = "New prompt";
|
|
370
|
+
agent.state.model = getModel("openai", "gpt-4o");
|
|
371
|
+
agent.state.thinkingLevel = "medium";
|
|
372
|
+
agent.state.tools = [myTool];
|
|
373
|
+
agent.toolExecution = "sequential";
|
|
374
|
+
agent.beforeToolCall = async ({ toolCall }) => undefined;
|
|
375
|
+
agent.afterToolCall = async ({ toolCall, result }) => undefined;
|
|
376
|
+
agent.shouldStopAfterTurn = async ({ context }) => shouldCompactBeforeNextTurn(context.messages);
|
|
377
|
+
agent.state.messages = newMessages; // top-level array is copied
|
|
378
|
+
agent.state.messages.push(message);
|
|
379
|
+
agent.reset(); // clears transcript, runtime state, and both queues
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Session and Thinking Budgets
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
agent.sessionId = "session-123";
|
|
386
|
+
|
|
387
|
+
agent.thinkingBudgets = {
|
|
388
|
+
minimal: 128,
|
|
389
|
+
low: 512,
|
|
390
|
+
medium: 1024,
|
|
391
|
+
high: 2048,
|
|
392
|
+
};
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Control
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
agent.abort(); // Cancel current operation
|
|
399
|
+
await agent.waitForIdle(); // Wait for completion (settles after awaited agent_end listeners)
|
|
400
|
+
agent.signal; // Active run AbortSignal, if any
|
|
401
|
+
agent.hasQueuedMessages(); // True when either queue still holds messages
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Events
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
const unsubscribe = agent.subscribe(async (event, signal) => {
|
|
408
|
+
if (event.type === "agent_end") {
|
|
409
|
+
// Final barrier work for the run
|
|
410
|
+
await flushSessionState(signal);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
unsubscribe();
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Steering and Follow-up
|
|
417
|
+
|
|
418
|
+
Steering messages let you interrupt the agent while tools are running. Follow-up
|
|
419
|
+
messages let you queue work after the agent would otherwise stop.
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
agent.steeringMode = "one-at-a-time";
|
|
423
|
+
agent.followUpMode = "one-at-a-time";
|
|
424
|
+
|
|
425
|
+
// While agent is running tools
|
|
426
|
+
agent.steer({
|
|
427
|
+
role: "user",
|
|
428
|
+
content: [{ type: "text", text: "Stop! Do this instead." }],
|
|
429
|
+
timestamp: Date.now(),
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// After the agent finishes its current work
|
|
433
|
+
agent.followUp({
|
|
434
|
+
role: "user",
|
|
435
|
+
content: [{ type: "text", text: "Also summarize the result." }],
|
|
436
|
+
timestamp: Date.now(),
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const steeringMode = agent.steeringMode;
|
|
440
|
+
const followUpMode = agent.followUpMode;
|
|
441
|
+
|
|
442
|
+
agent.clearSteeringQueue();
|
|
443
|
+
agent.clearFollowUpQueue();
|
|
444
|
+
agent.clearAllQueues();
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Use clearSteeringQueue, clearFollowUpQueue, or clearAllQueues to drop queued messages.
|
|
448
|
+
|
|
449
|
+
When steering messages are detected after a turn completes:
|
|
450
|
+
|
|
451
|
+
1. All tool calls from the current assistant message have already finished
|
|
452
|
+
2. Steering messages are injected
|
|
453
|
+
3. The LLM responds on the next turn
|
|
454
|
+
|
|
455
|
+
Follow-up messages are checked only when there are no more tool calls and no
|
|
456
|
+
steering messages. If any are queued, they are injected and another turn runs.
|
|
457
|
+
|
|
458
|
+
Queue modes: `"one-at-a-time"` (default) drains only the oldest queued message per
|
|
459
|
+
drain point; `"all"` drains every queued message at that point.
|
|
460
|
+
|
|
461
|
+
## Custom Message Types
|
|
462
|
+
|
|
463
|
+
Extend `AgentMessage` via declaration merging:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
declare module "@m4ike1/ion-agent-core" {
|
|
467
|
+
interface CustomAgentMessages {
|
|
468
|
+
notification: { role: "notification"; text: string; timestamp: number };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Now valid
|
|
473
|
+
const msg: AgentMessage = { role: "notification", text: "Info", timestamp: Date.now() };
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Handle custom types in `convertToLlm`:
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
const agent = new Agent({
|
|
480
|
+
streamFn: models.streamSimple.bind(models),
|
|
481
|
+
convertToLlm: (messages) => messages.flatMap(m => {
|
|
482
|
+
if (m.role === "notification") return []; // Filter out
|
|
483
|
+
return [m];
|
|
484
|
+
}),
|
|
485
|
+
});
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
## Tools
|
|
489
|
+
|
|
490
|
+
Define tools using `AgentTool`:
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
import { Type } from "typebox";
|
|
494
|
+
|
|
495
|
+
const readFileTool: AgentTool = {
|
|
496
|
+
name: "read_file",
|
|
497
|
+
label: "Read File", // For UI display
|
|
498
|
+
description: "Read a file's contents",
|
|
499
|
+
parameters: Type.Object({
|
|
500
|
+
path: Type.String({ description: "File path" }),
|
|
501
|
+
}),
|
|
502
|
+
// Override execution mode for this tool (optional).
|
|
503
|
+
// "sequential" forces the entire batch to run one at a time.
|
|
504
|
+
// "parallel" allows concurrent execution with other tool calls.
|
|
505
|
+
// If omitted, the global toolExecution config applies.
|
|
506
|
+
executionMode: "sequential",
|
|
507
|
+
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
508
|
+
const content = await fs.readFile(params.path, "utf-8");
|
|
509
|
+
|
|
510
|
+
// Optional: stream progress
|
|
511
|
+
onUpdate?.({ content: [{ type: "text", text: "Reading..." }], details: {} });
|
|
512
|
+
|
|
513
|
+
// Optional: add `terminate: true` here to skip the automatic follow-up LLM call
|
|
514
|
+
// when every finalized tool result in the batch does the same.
|
|
515
|
+
return {
|
|
516
|
+
content: [{ type: "text", text: content }],
|
|
517
|
+
details: { path: params.path, size: content.length },
|
|
518
|
+
};
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
agent.state.tools = [readFileTool];
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
`AgentTool` also supports `prepareArguments` (compatibility shim normalizing raw
|
|
526
|
+
tool-call arguments to the schema before validation) and `replay` (`"never"` |
|
|
527
|
+
`"safe"`, recovery policy for a durable effect whose outcome is unknown).
|
|
528
|
+
|
|
529
|
+
### Error Handling
|
|
530
|
+
|
|
531
|
+
**Throw an error** when a tool fails. Do not return error messages as content.
|
|
532
|
+
|
|
533
|
+
```typescript
|
|
534
|
+
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
535
|
+
if (!fs.existsSync(params.path)) {
|
|
536
|
+
throw new Error(`File not found: ${params.path}`);
|
|
537
|
+
}
|
|
538
|
+
// Return content only on success
|
|
539
|
+
return { content: [{ type: "text", text: "..." }] };
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Thrown errors are caught by the agent and reported to the LLM as tool errors with
|
|
544
|
+
`isError: true`.
|
|
545
|
+
|
|
546
|
+
Return `terminate: true` from `execute()`, a blocked `beforeToolCall`, or
|
|
547
|
+
`afterToolCall` to hint that the agent should stop after the current tool batch.
|
|
548
|
+
This only takes effect when every finalized tool result in the batch is
|
|
549
|
+
terminating. The hint is runtime-only; emitted `toolResult` transcript messages
|
|
550
|
+
remain standard LLM tool results.
|
|
551
|
+
|
|
552
|
+
## Proxy Usage
|
|
553
|
+
|
|
554
|
+
For browser apps that proxy through a backend:
|
|
555
|
+
|
|
556
|
+
```typescript
|
|
557
|
+
import { Agent, streamProxy } from "@m4ike1/ion-agent-core";
|
|
558
|
+
|
|
559
|
+
const agent = new Agent({
|
|
560
|
+
streamFn: (model, context, options) =>
|
|
561
|
+
streamProxy(model, context, {
|
|
562
|
+
...options,
|
|
563
|
+
authToken: "...",
|
|
564
|
+
proxyUrl: "https://your-server.com",
|
|
565
|
+
}),
|
|
566
|
+
});
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
`streamProxy` POSTs `{ model, context, options }` to `${proxyUrl}/api/stream` with a
|
|
570
|
+
`Bearer` auth token and reconstructs the assistant stream client-side. Only
|
|
571
|
+
serializable stream options are forwarded (temperature, samplingParams, maxTokens,
|
|
572
|
+
reasoning, cacheRetention, sessionId, headers, metadata, transport,
|
|
573
|
+
thinkingBudgets, maxRetryDelayMs).
|
|
574
|
+
|
|
575
|
+
## Low-Level API
|
|
576
|
+
|
|
577
|
+
For direct control without the Agent class:
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
import { agentLoop, agentLoopContinue } from "@m4ike1/ion-agent-core";
|
|
581
|
+
|
|
582
|
+
const context: AgentContext = {
|
|
583
|
+
systemPrompt: "You are helpful.",
|
|
584
|
+
messages: [],
|
|
585
|
+
tools: [],
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const config: AgentLoopConfig = {
|
|
589
|
+
model: getModel("openai", "gpt-4o"),
|
|
590
|
+
convertToLlm: (msgs) => msgs.filter(m => ["user", "assistant", "toolResult"].includes(m.role)),
|
|
591
|
+
toolExecution: "parallel", // overridden by per-tool executionMode if set
|
|
592
|
+
beforeToolCall: async ({ toolCall, args, context }) => undefined,
|
|
593
|
+
afterToolCall: async ({ toolCall, result, isError, context }) => undefined,
|
|
594
|
+
shouldStopAfterTurn: async ({ message, toolResults, context, newMessages }) => {
|
|
595
|
+
return shouldCompactBeforeNextTurn(context.messages);
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const userMessage = { role: "user", content: [{ type: "text", text: "Hello" }], timestamp: Date.now() };
|
|
600
|
+
|
|
601
|
+
const streamFn = models.streamSimple.bind(models);
|
|
602
|
+
for await (const event of agentLoop([userMessage], context, config, undefined, streamFn)) {
|
|
603
|
+
console.log(event.type);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Continue from existing context
|
|
607
|
+
for await (const event of agentLoopContinue(context, config, undefined, streamFn)) {
|
|
608
|
+
console.log(event.type);
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
`runAgentLoop` / `runAgentLoopContinue` are the promise-based variants: same
|
|
613
|
+
arguments plus an `emit` sink, resolving to the new messages.
|
|
614
|
+
|
|
615
|
+
These low-level streams are observational. They preserve event order, but they do
|
|
616
|
+
not wait for your async event handling to settle before later producer phases
|
|
617
|
+
continue. If you need message processing to act as a barrier before tool preflight,
|
|
618
|
+
use the `Agent` class instead of raw `agentLoop()` or `agentLoopContinue()`.
|
|
619
|
+
|
|
620
|
+
## License
|
|
621
|
+
|
|
622
|
+
MIT
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent loop that works with AgentMessage throughout.
|
|
3
|
+
* Transforms to Message[] only at the LLM call boundary.
|
|
4
|
+
*/
|
|
5
|
+
import { EventStream } from "@m4ike1/ion-ai";
|
|
6
|
+
import type { AgentContext, AgentEvent, AgentLoopConfig, AgentMessage, StreamFn } from "./types.ts";
|
|
7
|
+
export type AgentEventSink = (event: AgentEvent) => Promise<void> | void;
|
|
8
|
+
/**
|
|
9
|
+
* Start an agent loop with a new prompt message.
|
|
10
|
+
* The prompt is added to the context and events are emitted for it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function agentLoop(prompts: AgentMessage[], context: AgentContext, config: AgentLoopConfig, signal: AbortSignal | undefined, streamFn: StreamFn): EventStream<AgentEvent, AgentMessage[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Continue an agent loop from the current context without adding a new message.
|
|
15
|
+
* Used for retries - context already has user message or tool results.
|
|
16
|
+
*
|
|
17
|
+
* **Important:** The last message in context must convert to a `user` or `toolResult` message
|
|
18
|
+
* via `convertToLlm`. If it doesn't, the LLM provider will reject the request.
|
|
19
|
+
* This cannot be validated here since `convertToLlm` is only called once per turn.
|
|
20
|
+
*/
|
|
21
|
+
export declare function agentLoopContinue(context: AgentContext, config: AgentLoopConfig, signal: AbortSignal | undefined, streamFn: StreamFn): EventStream<AgentEvent, AgentMessage[]>;
|
|
22
|
+
export declare function runAgentLoop(prompts: AgentMessage[], context: AgentContext, config: AgentLoopConfig, emit: AgentEventSink, signal: AbortSignal | undefined, streamFn: StreamFn): Promise<AgentMessage[]>;
|
|
23
|
+
export declare function runAgentLoopContinue(context: AgentContext, config: AgentLoopConfig, emit: AgentEventSink, signal: AbortSignal | undefined, streamFn: StreamFn): Promise<AgentMessage[]>;
|
|
24
|
+
//# sourceMappingURL=agent-loop.d.ts.map
|