@autonome-research/thread-phase 3.0.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/LICENSE +21 -0
- package/README.md +226 -0
- package/dist/agent/index.d.ts +28 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +28 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/openai-adapter.d.ts +15 -0
- package/dist/agent/openai-adapter.d.ts.map +1 -0
- package/dist/agent/openai-adapter.js +57 -0
- package/dist/agent/openai-adapter.js.map +1 -0
- package/dist/agent/parse-json.d.ts +12 -0
- package/dist/agent/parse-json.d.ts.map +1 -0
- package/dist/agent/parse-json.js +31 -0
- package/dist/agent/parse-json.js.map +1 -0
- package/dist/agent/retry.d.ts +15 -0
- package/dist/agent/retry.d.ts.map +1 -0
- package/dist/agent/retry.js +35 -0
- package/dist/agent/retry.js.map +1 -0
- package/dist/agent/runner.d.ts +25 -0
- package/dist/agent/runner.d.ts.map +1 -0
- package/dist/agent/runner.js +270 -0
- package/dist/agent/runner.js.map +1 -0
- package/dist/agent/stream-consumer.d.ts +57 -0
- package/dist/agent/stream-consumer.d.ts.map +1 -0
- package/dist/agent/stream-consumer.js +126 -0
- package/dist/agent/stream-consumer.js.map +1 -0
- package/dist/agent/types.d.ts +135 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +9 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/agent-runner.d.ts +10 -0
- package/dist/agent-runner.d.ts.map +1 -0
- package/dist/agent-runner.js +10 -0
- package/dist/agent-runner.js.map +1 -0
- package/dist/agents/capability.d.ts +36 -0
- package/dist/agents/capability.d.ts.map +1 -0
- package/dist/agents/capability.js +51 -0
- package/dist/agents/capability.js.map +1 -0
- package/dist/agents/event-bus.d.ts +20 -0
- package/dist/agents/event-bus.d.ts.map +1 -0
- package/dist/agents/event-bus.js +40 -0
- package/dist/agents/event-bus.js.map +1 -0
- package/dist/agents/index.d.ts +23 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +33 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/inference-adapter.d.ts +52 -0
- package/dist/agents/inference-adapter.d.ts.map +1 -0
- package/dist/agents/inference-adapter.js +209 -0
- package/dist/agents/inference-adapter.js.map +1 -0
- package/dist/agents/job-store-bridge.d.ts +44 -0
- package/dist/agents/job-store-bridge.d.ts.map +1 -0
- package/dist/agents/job-store-bridge.js +58 -0
- package/dist/agents/job-store-bridge.js.map +1 -0
- package/dist/agents/memory.d.ts +40 -0
- package/dist/agents/memory.d.ts.map +1 -0
- package/dist/agents/memory.js +14 -0
- package/dist/agents/memory.js.map +1 -0
- package/dist/agents/protocol.d.ts +302 -0
- package/dist/agents/protocol.d.ts.map +1 -0
- package/dist/agents/protocol.js +36 -0
- package/dist/agents/protocol.js.map +1 -0
- package/dist/agents/run-helpers.d.ts +70 -0
- package/dist/agents/run-helpers.d.ts.map +1 -0
- package/dist/agents/run-helpers.js +131 -0
- package/dist/agents/run-helpers.js.map +1 -0
- package/dist/agents/serialize-error.d.ts +18 -0
- package/dist/agents/serialize-error.d.ts.map +1 -0
- package/dist/agents/serialize-error.js +27 -0
- package/dist/agents/serialize-error.js.map +1 -0
- package/dist/agents/structured-output.d.ts +90 -0
- package/dist/agents/structured-output.d.ts.map +1 -0
- package/dist/agents/structured-output.js +101 -0
- package/dist/agents/structured-output.js.map +1 -0
- package/dist/agents/test-utils/conformance.d.ts +59 -0
- package/dist/agents/test-utils/conformance.d.ts.map +1 -0
- package/dist/agents/test-utils/conformance.js +207 -0
- package/dist/agents/test-utils/conformance.js.map +1 -0
- package/dist/agents/test-utils/index.d.ts +12 -0
- package/dist/agents/test-utils/index.d.ts.map +1 -0
- package/dist/agents/test-utils/index.js +12 -0
- package/dist/agents/test-utils/index.js.map +1 -0
- package/dist/agents/test-utils/mock-agent.d.ts +66 -0
- package/dist/agents/test-utils/mock-agent.d.ts.map +1 -0
- package/dist/agents/test-utils/mock-agent.js +244 -0
- package/dist/agents/test-utils/mock-agent.js.map +1 -0
- package/dist/agents/thread.d.ts +57 -0
- package/dist/agents/thread.d.ts.map +1 -0
- package/dist/agents/thread.js +128 -0
- package/dist/agents/thread.js.map +1 -0
- package/dist/agents/turn-accumulator.d.ts +94 -0
- package/dist/agents/turn-accumulator.d.ts.map +1 -0
- package/dist/agents/turn-accumulator.js +150 -0
- package/dist/agents/turn-accumulator.js.map +1 -0
- package/dist/agents/with-memory.d.ts +55 -0
- package/dist/agents/with-memory.d.ts.map +1 -0
- package/dist/agents/with-memory.js +155 -0
- package/dist/agents/with-memory.js.map +1 -0
- package/dist/agents/with-thread.d.ts +45 -0
- package/dist/agents/with-thread.d.ts.map +1 -0
- package/dist/agents/with-thread.js +70 -0
- package/dist/agents/with-thread.js.map +1 -0
- package/dist/cache.d.ts +47 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +81 -0
- package/dist/cache.js.map +1 -0
- package/dist/context/compressor.d.ts +36 -0
- package/dist/context/compressor.d.ts.map +1 -0
- package/dist/context/compressor.js +158 -0
- package/dist/context/compressor.js.map +1 -0
- package/dist/context/index.d.ts +4 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +4 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/result-capper.d.ts +32 -0
- package/dist/context/result-capper.d.ts.map +1 -0
- package/dist/context/result-capper.js +50 -0
- package/dist/context/result-capper.js.map +1 -0
- package/dist/context/token-budget.d.ts +81 -0
- package/dist/context/token-budget.d.ts.map +1 -0
- package/dist/context/token-budget.js +99 -0
- package/dist/context/token-budget.js.map +1 -0
- package/dist/helpers/caller.d.ts +18 -0
- package/dist/helpers/caller.d.ts.map +1 -0
- package/dist/helpers/caller.js +40 -0
- package/dist/helpers/caller.js.map +1 -0
- package/dist/helpers/hook.d.ts +73 -0
- package/dist/helpers/hook.d.ts.map +1 -0
- package/dist/helpers/hook.js +244 -0
- package/dist/helpers/hook.js.map +1 -0
- package/dist/helpers/index.d.ts +12 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +11 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/one-shot.d.ts +27 -0
- package/dist/helpers/one-shot.d.ts.map +1 -0
- package/dist/helpers/one-shot.js +43 -0
- package/dist/helpers/one-shot.js.map +1 -0
- package/dist/helpers/schedule.d.ts +59 -0
- package/dist/helpers/schedule.d.ts.map +1 -0
- package/dist/helpers/schedule.js +118 -0
- package/dist/helpers/schedule.js.map +1 -0
- package/dist/helpers/types.d.ts +34 -0
- package/dist/helpers/types.d.ts.map +1 -0
- package/dist/helpers/types.js +11 -0
- package/dist/helpers/types.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/inference.d.ts +27 -0
- package/dist/inference.d.ts.map +1 -0
- package/dist/inference.js +34 -0
- package/dist/inference.js.map +1 -0
- package/dist/messages.d.ts +64 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +17 -0
- package/dist/messages.js.map +1 -0
- package/dist/orchestrator.d.ts +56 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +62 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/patterns/bounded-fanout-of.d.ts +61 -0
- package/dist/patterns/bounded-fanout-of.d.ts.map +1 -0
- package/dist/patterns/bounded-fanout-of.js +142 -0
- package/dist/patterns/bounded-fanout-of.js.map +1 -0
- package/dist/patterns/bounded-fanout.d.ts +111 -0
- package/dist/patterns/bounded-fanout.d.ts.map +1 -0
- package/dist/patterns/bounded-fanout.js +151 -0
- package/dist/patterns/bounded-fanout.js.map +1 -0
- package/dist/patterns/index.d.ts +14 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +13 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/intent-gate.d.ts +27 -0
- package/dist/patterns/intent-gate.d.ts.map +1 -0
- package/dist/patterns/intent-gate.js +32 -0
- package/dist/patterns/intent-gate.js.map +1 -0
- package/dist/patterns/match.d.ts +30 -0
- package/dist/patterns/match.d.ts.map +1 -0
- package/dist/patterns/match.js +58 -0
- package/dist/patterns/match.js.map +1 -0
- package/dist/patterns/parallel-fanout.d.ts +28 -0
- package/dist/patterns/parallel-fanout.d.ts.map +1 -0
- package/dist/patterns/parallel-fanout.js +24 -0
- package/dist/patterns/parallel-fanout.js.map +1 -0
- package/dist/patterns/parallel-phases.d.ts +27 -0
- package/dist/patterns/parallel-phases.d.ts.map +1 -0
- package/dist/patterns/parallel-phases.js +77 -0
- package/dist/patterns/parallel-phases.js.map +1 -0
- package/dist/patterns/preflight-confidence.d.ts +20 -0
- package/dist/patterns/preflight-confidence.d.ts.map +1 -0
- package/dist/patterns/preflight-confidence.js +38 -0
- package/dist/patterns/preflight-confidence.js.map +1 -0
- package/dist/patterns/spot-check.d.ts +19 -0
- package/dist/patterns/spot-check.d.ts.map +1 -0
- package/dist/patterns/spot-check.js +33 -0
- package/dist/patterns/spot-check.js.map +1 -0
- package/dist/patterns/sub-pipeline.d.ts +84 -0
- package/dist/patterns/sub-pipeline.d.ts.map +1 -0
- package/dist/patterns/sub-pipeline.js +90 -0
- package/dist/patterns/sub-pipeline.js.map +1 -0
- package/dist/patterns/synthesize-with-followup.d.ts +35 -0
- package/dist/patterns/synthesize-with-followup.d.ts.map +1 -0
- package/dist/patterns/synthesize-with-followup.js +45 -0
- package/dist/patterns/synthesize-with-followup.js.map +1 -0
- package/dist/patterns/while-condition.d.ts +31 -0
- package/dist/patterns/while-condition.d.ts.map +1 -0
- package/dist/patterns/while-condition.js +59 -0
- package/dist/patterns/while-condition.js.map +1 -0
- package/dist/patterns/with-retry.d.ts +37 -0
- package/dist/patterns/with-retry.d.ts.map +1 -0
- package/dist/patterns/with-retry.js +73 -0
- package/dist/patterns/with-retry.js.map +1 -0
- package/dist/phase.d.ts +78 -0
- package/dist/phase.d.ts.map +1 -0
- package/dist/phase.js +36 -0
- package/dist/phase.js.map +1 -0
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +4 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/job-runner.d.ts +67 -0
- package/dist/session/job-runner.d.ts.map +1 -0
- package/dist/session/job-runner.js +131 -0
- package/dist/session/job-runner.js.map +1 -0
- package/dist/session/job-store.d.ts +98 -0
- package/dist/session/job-store.d.ts.map +1 -0
- package/dist/session/job-store.js +37 -0
- package/dist/session/job-store.js.map +1 -0
- package/dist/session/sqlite-job-store.d.ts +40 -0
- package/dist/session/sqlite-job-store.d.ts.map +1 -0
- package/dist/session/sqlite-job-store.js +200 -0
- package/dist/session/sqlite-job-store.js.map +1 -0
- package/dist/session/sse.d.ts +60 -0
- package/dist/session/sse.d.ts.map +1 -0
- package/dist/session/sse.js +97 -0
- package/dist/session/sse.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +44 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +74 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/triggers/index.d.ts +15 -0
- package/dist/triggers/index.d.ts.map +1 -0
- package/dist/triggers/index.js +14 -0
- package/dist/triggers/index.js.map +1 -0
- package/dist/triggers/run-trigger.d.ts +86 -0
- package/dist/triggers/run-trigger.d.ts.map +1 -0
- package/dist/triggers/run-trigger.js +146 -0
- package/dist/triggers/run-trigger.js.map +1 -0
- package/dist/triggers/timer-trigger.d.ts +46 -0
- package/dist/triggers/timer-trigger.d.ts.map +1 -0
- package/dist/triggers/timer-trigger.js +74 -0
- package/dist/triggers/timer-trigger.js.map +1 -0
- package/dist/triggers/types.d.ts +61 -0
- package/dist/triggers/types.d.ts.map +1 -0
- package/dist/triggers/types.js +23 -0
- package/dist/triggers/types.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 thread-phase contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# thread-phase
|
|
2
|
+
|
|
3
|
+
A TypeScript framework that composes deterministic phases over **heterogeneous agents** — the iterated tool-use loop against OpenAI-compatible inference for raw model calls, the `AgentAdapter` protocol for delegating to ready agents (Claude Code, Hermes, Codex, OpenClaw, Anthropic SDK). Multi-phase pipelines with a typed shared context, persistent event logs, and concurrency-capped fanout.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @autonome-research/thread-phase
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
> **Generating thread-phase code with an LLM agent?** See [`AGENTS.md`](./AGENTS.md) — a self-contained reference covering the mental model, a copy-paste template, and explicit anti-patterns. Claude Code users can also install [`SKILL.md`](./SKILL.md) into `~/.claude/skills/thread-phase/` so the guidance auto-loads.
|
|
10
|
+
|
|
11
|
+
## Use cases
|
|
12
|
+
|
|
13
|
+
thread-phase is designed for two shapes:
|
|
14
|
+
|
|
15
|
+
1. **Agent-authored automations.** When an autonomous agent (Claude Code, Hermes, Cursor, etc.) sets up a recurring task — a cron job, a systemd timer, a CI step — the structuring usually happens at prompt-write time but the execution still relies on the agent re-deriving its plan at run time. thread-phase gives the structuring agent a typed phase boundary to encode the deterministic parts of the pipeline (ordering, fan-out, ctx flow, post-condition checks) while leaving the run-time agent free to make judgment calls inside each phase. The cron line ends up being a plain `npx tsx ...` invocation; no prompt at run time. See [`examples/agent-authored-cron.ts`](./examples/agent-authored-cron.ts).
|
|
16
|
+
|
|
17
|
+
2. **Mini-workflows inside larger DAG frameworks.** Temporal, LangGraph, Inngest, and similar frameworks are built for distributed DAG orchestration; they're heavyweight when a single node needs to run a small multi-step agent loop with its own concurrency cap, retry, and event log. thread-phase fits as the *inside* of one node — Temporal owns the workflow topology and durable state across machines; thread-phase owns the streaming tool-use loop and per-node phase composition. This composes cleanly because thread-phase's `runAgentWithTools` and `runPipeline` don't assume they own the event loop or persistence layer.
|
|
18
|
+
|
|
19
|
+
It's also useful as a standalone pipeline runner (`JobRunner` + sqlite event log + SSE streaming) for batch-processing workloads that don't need either of the above.
|
|
20
|
+
|
|
21
|
+
## Out of scope
|
|
22
|
+
|
|
23
|
+
- DAG / graph framework features (cross-node dependency graphs, declarative edge routing, distributed scheduling). Use Temporal/LangGraph/Inngest, embedding thread-phase inside their nodes.
|
|
24
|
+
- Anthropic content-block model (vision, citations, extended thinking). Use the Anthropic SDK directly.
|
|
25
|
+
- Multi-modal inputs.
|
|
26
|
+
- Long-document summarization (the bundled compressor uses opaque markers for old tool results — known weakness for hierarchical summarization, see [ROADMAP](./ROADMAP.md)).
|
|
27
|
+
|
|
28
|
+
## Quickstart
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {
|
|
32
|
+
runAgentWithTools,
|
|
33
|
+
ToolRegistry,
|
|
34
|
+
createInferenceClient,
|
|
35
|
+
} from '@autonome-research/thread-phase';
|
|
36
|
+
|
|
37
|
+
const tools = new ToolRegistry().register(
|
|
38
|
+
{
|
|
39
|
+
name: 'add',
|
|
40
|
+
description: 'Add two integers',
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: 'object',
|
|
43
|
+
properties: { a: { type: 'number' }, b: { type: 'number' } },
|
|
44
|
+
required: ['a', 'b'],
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
async (args) => String((args.a as number) + (args.b as number)),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const result = await runAgentWithTools(
|
|
52
|
+
{
|
|
53
|
+
name: 'math',
|
|
54
|
+
systemPrompt: 'Use the add tool. Reply with just the number.',
|
|
55
|
+
model: 'qwen3.6-27b',
|
|
56
|
+
tools: tools.definitions(),
|
|
57
|
+
maxToolRounds: 5,
|
|
58
|
+
maxTokens: 256,
|
|
59
|
+
},
|
|
60
|
+
[{ role: 'user', content: 'What is 17 + 25?' }],
|
|
61
|
+
{ client: createInferenceClient(), toolExecutor: tools },
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
result.text; // "42"
|
|
65
|
+
result.finishReason; // "stop" | "length" | "tool_calls" | ...
|
|
66
|
+
result.usage; // { promptTokens, completionTokens, totalTokens }
|
|
67
|
+
result.executedToolCalls; // [{ id, name: 'add', input: { a: 17, b: 25 } }]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Architecture
|
|
71
|
+
|
|
72
|
+
Three primitives plus one extension surface.
|
|
73
|
+
|
|
74
|
+
### `runAgentWithTools(config, messages, options) → AgentRunResult`
|
|
75
|
+
|
|
76
|
+
The streaming tool-use loop. Sends a chat-completions request with `stream: true`, accumulates content and tool-call deltas, dispatches tools through `options.toolExecutor`, loops until the model produces final text or hits `config.maxToolRounds`. Returns a structured result:
|
|
77
|
+
|
|
78
|
+
- `text` — final text output
|
|
79
|
+
- `finishReason` — `'stop' | 'length' | 'tool_calls' | 'content_filter' | 'function_call' | 'error' | 'unknown'`. Branch on `'length'` to detect truncation.
|
|
80
|
+
- `usage` — `{ promptTokens, completionTokens, totalTokens }`, summed across rounds
|
|
81
|
+
- `executedToolCalls` — every tool call the model actually executed (id, name, parsed args)
|
|
82
|
+
- `activity` — string log of internal events
|
|
83
|
+
|
|
84
|
+
`options.signal` propagates an `AbortSignal` into the inference call. `options.onStreamEvent` receives `content_delta`, `tool_call_started`, `tool_call_complete`, and `round_complete` events as they arrive. `options.verifyResult` is a hook that runs once before returning — it can transform the result or throw to mark the run as failed; use it to validate the agent's claimed output against `executedToolCalls`.
|
|
85
|
+
|
|
86
|
+
### `Phase<TCtx>` + `runPipeline(phases, ctx)`
|
|
87
|
+
|
|
88
|
+
A `Phase` is an async generator that reads from a shared `ctx`, yields events, and writes outputs back to `ctx`. A pipeline is an array of phases run in order:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
interface Phase<TCtx extends BasePipelineContext> {
|
|
92
|
+
readonly name: string;
|
|
93
|
+
run(ctx: TCtx): AsyncGenerator<PipelineEvent, void>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for await (const event of runPipeline([phaseA, phaseB, phaseC], ctx)) {
|
|
97
|
+
// each phase yields events; the orchestrator owns the terminal 'done' / 'error'
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`requireCtx(ctx, key, phaseName)` is the loud-precondition helper — fails with the field name if a prerequisite phase didn't populate the field. Use it at the top of every phase that reads from ctx.
|
|
102
|
+
|
|
103
|
+
`ctx.stop = { reason }` halts the pipeline cleanly. Loops, conditional branches, and parallel sub-flows are composed in TypeScript rather than declared in a graph language; the [`patterns/`](./docs/patterns.md) module names the recurring shapes.
|
|
104
|
+
|
|
105
|
+
### `JobRunner` + `JobStore`
|
|
106
|
+
|
|
107
|
+
`JobRunner` wraps a pipeline run with a persistent event log (`JobStore`, sqlite-backed by default), live event emission for SSE consumers, and per-job cancellation:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const runner = new JobRunner(new SqliteJobStore('./jobs.db'));
|
|
111
|
+
const jobId = runner.create('my-pipeline', input);
|
|
112
|
+
|
|
113
|
+
// wire SIGTERM to runner.cancel so a stuck inference call exits cleanly
|
|
114
|
+
process.on('SIGTERM', () => runner.cancel(jobId, 'systemd timeout'));
|
|
115
|
+
|
|
116
|
+
await runner.run(jobId, [phaseA, phaseB], ctx);
|
|
117
|
+
// events persisted; consumers can replay via store.getEvents(jobId, afterId)
|
|
118
|
+
// or subscribe live via runner.on(`job:${jobId}`, ...)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`JobRunner.signalFor(jobId)` exposes the `AbortSignal` so phase code can wire it into individual `runAgentWithTools` calls — without that wiring, cancellation only halts between phases.
|
|
122
|
+
|
|
123
|
+
The interface is sync by design (sqlite hot path; fire-and-forget event writes). Async backends will land as an additive `JobStoreAsync` interface if/when needed; see [ROADMAP](./ROADMAP.md).
|
|
124
|
+
|
|
125
|
+
### `AgentAdapter` — the extension surface
|
|
126
|
+
|
|
127
|
+
`AgentAdapter` is the protocol every ready-agent integration speaks. The in-tree `inferenceAgent` wraps `runAgentWithTools`; sibling implementations in [`@autonome-research/thread-phase-agents`](../thread-phase-agents) wrap `hermes`, `openclaw`, `claude`, the OpenAI Responses API (Codex), and the Anthropic SDK directly.
|
|
128
|
+
|
|
129
|
+
Every adapter returns the same shape:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
interface AgentRun {
|
|
133
|
+
readonly events: AsyncIterable<AgentEvent>; // single-consumer stream
|
|
134
|
+
readonly result: Promise<AgentRunResult>; // always resolves, never rejects
|
|
135
|
+
abort(reason?: string): void;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Canonical events: `agent_start | text | thinking | tool_call | tool_result | turn_end | agent_end | error | native`. Every event carries a `source` field (the adapter's id) so heterogeneous adapter events flow through one `AgentEventBus` without losing provenance.
|
|
140
|
+
|
|
141
|
+
Conversation state across phases lives in the `Thread` primitive — canonical events plus per-adapter resume tokens. Same-adapter chains (claude-code → claude-code) resume natively via the adapter's session; cross-adapter chains render events back to text via `threadToMessages`.
|
|
142
|
+
|
|
143
|
+
Memory across runs is outsourced: `MemoryProvider` is just a TypeScript interface (`recall(scope, query?) / remember(scope, events)`). thread-phase ships no implementations; bind Honcho, Letta, Mem0, or a custom backend yourself. See [`examples/honcho-memory.ts`](./examples/honcho-memory.ts).
|
|
144
|
+
|
|
145
|
+
### `Trigger` — the entry-point abstraction
|
|
146
|
+
|
|
147
|
+
`Trigger<TInput>` is the protocol every signal source implements: timers, webhooks, queue consumers, file watchers, message brokers. Each trigger yields `TriggerEvent<TInput>` with `{ id, occurredAt, input, metadata }`. `runTrigger(trigger, factory, options)` is the canonical consumer — it reads events, dispatches pipelines (optionally through a `JobRunner`), enforces a concurrency cap with backpressure, and isolates per-event failures.
|
|
148
|
+
|
|
149
|
+
Core ships `TimerTrigger` only. HTTP/queue/file-watch transports stay in `examples/triggers/` as recipes — wrap your favorite framework into the protocol, don't make thread-phase ship transports.
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { TimerTrigger, runTrigger } from '@autonome-research/thread-phase/triggers';
|
|
153
|
+
|
|
154
|
+
const trigger = new TimerTrigger({ intervalMs: 15 * 60_000, name: 'every-15m' });
|
|
155
|
+
const handle = runTrigger(
|
|
156
|
+
trigger,
|
|
157
|
+
() => ({ phases: [myPipeline], ctx: { cache: new PipelineCache() } }),
|
|
158
|
+
{ jobRunner: runner, jobStore: store },
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
process.on('SIGTERM', () => void handle.stop());
|
|
162
|
+
await handle.done;
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Patterns
|
|
166
|
+
|
|
167
|
+
In `@autonome-research/thread-phase/patterns`:
|
|
168
|
+
|
|
169
|
+
| Pattern | Shape |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `boundedFanout` | N items, free-function runner per item, capped concurrency, results in input order |
|
|
172
|
+
| `boundedFanoutOf` | Same, but the runner is an `AgentAdapter` + buildConfig — automatic event-bus propagation |
|
|
173
|
+
| `parallelPhases` | Several phases run concurrently as one composite |
|
|
174
|
+
| `intentGate` | Cheap classifier decides whether the rest of the pipeline runs |
|
|
175
|
+
| `whileCondition` | Loop a body of phases while an async predicate holds, with a max-iteration cap |
|
|
176
|
+
| `match` | Keyed dispatch — route to one of N phase lists by selector key |
|
|
177
|
+
| `withRetry` | Higher-order wrapper retrying a phase with exponential backoff on failure |
|
|
178
|
+
|
|
179
|
+
See [`docs/patterns.md`](./docs/patterns.md) for selection guidance ("I want to do X" → "use Y"). v3.0.0 trimmed five patterns (`parallelFanout`, `streamingBoundedFanout`, `preflightConfidence`, `synthesizeWithFollowup`, `spotCheck`) into composition recipes — see [`docs/recipes.md`](./docs/recipes.md) for paste-in equivalents.
|
|
180
|
+
|
|
181
|
+
## Configuration
|
|
182
|
+
|
|
183
|
+
Environment-driven by default (override in code via `loadInferenceConfig({ ... })`):
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
INFERENCE_BASE_URL=http://localhost:8000/v1
|
|
187
|
+
INFERENCE_API_KEY=not-needed-for-local-vllm
|
|
188
|
+
INFERENCE_MODEL=qwen3.6-27b
|
|
189
|
+
INFERENCE_CONTEXT_LENGTH=131072
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
For tool-using agents on vLLM, the server needs `--enable-auto-tool-choice --tool-call-parser <name>` matching the model's output format. If content shaped like a tool call arrives as plain text instead of structured `tool_calls`, the runner emits a `parser_mismatch_warning` activity entry.
|
|
193
|
+
|
|
194
|
+
## Examples
|
|
195
|
+
|
|
196
|
+
In [`examples/`](./examples), runnable via `npx tsx examples/<name>.ts`:
|
|
197
|
+
|
|
198
|
+
| File | Demonstrates |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `bare-agent.ts` | Single tool, single agent call, structured result |
|
|
201
|
+
| `multi-phase-pipeline.ts` | Linear pipeline with one parallel branch |
|
|
202
|
+
| `streaming-consumer.ts` | Content + tool-call deltas as they stream |
|
|
203
|
+
| `bounded-fanout.ts` | Per-item agent over a list, concurrency-capped |
|
|
204
|
+
| `sse-server.ts` | `JobRunner` + `streamToSSE` in an HTTP handler |
|
|
205
|
+
| `agent-authored-cron.ts` | End-to-end automation skeleton — fetch / triage / summarize / compose, with `verifyResult` and `JobRunner` |
|
|
206
|
+
| `honcho-memory.ts` | `MemoryProvider` bound to Honcho — recall before an agent call, remember after |
|
|
207
|
+
|
|
208
|
+
## Stability
|
|
209
|
+
|
|
210
|
+
v1.0.0 onward follows semver:
|
|
211
|
+
|
|
212
|
+
- **patch (1.0.x)** — bug fixes, no API changes
|
|
213
|
+
- **minor (1.x.0)** — additive changes (new patterns, new optional fields)
|
|
214
|
+
- **major (x.0.0)** — breaking changes
|
|
215
|
+
|
|
216
|
+
Items marked `@internal` in their JSDoc (e.g. `consumeStream`, `toOpenAIMessages`) are reachable for advanced callers but **not** covered by semver.
|
|
217
|
+
|
|
218
|
+
103 tests across 13 files. Validated in production by [`Code4me2/chiya-library`](https://github.com/Code4me2/chiya-library) — digest + librarian pipelines, hundreds of articles per day, on systemd timers.
|
|
219
|
+
|
|
220
|
+
## Contributing
|
|
221
|
+
|
|
222
|
+
Issues and PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md). For larger changes, open an issue first — the framework has a deliberately narrow scope and we'd rather discuss before code is written.
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent module barrel.
|
|
3
|
+
*
|
|
4
|
+
* Two tiers of exports:
|
|
5
|
+
*
|
|
6
|
+
* 1. Stable (covered by semver, won't break in minor/patch releases):
|
|
7
|
+
* `runAgentWithTools`, `parseJSON`, and the type surface
|
|
8
|
+
* (`AgentConfig`, `AgentRunResult`, `AgentRunnerOptions`,
|
|
9
|
+
* `AgentStreamEvent`, `FinishReason`, `UsageInfo`, `ActivityEntry`).
|
|
10
|
+
*
|
|
11
|
+
* 2. @internal (exported for advanced callers, NOT covered by semver):
|
|
12
|
+
* `consumeStream`, `looksLikeToolCallText`, `normalizeFinishReason`,
|
|
13
|
+
* `AccumulatedRound`, `toOpenAIMessages`, `toOpenAITools`,
|
|
14
|
+
* `isRetryableError`, `isAbortError`. These exist so callers building
|
|
15
|
+
* their own non-loop flows can reuse the building blocks; if you do,
|
|
16
|
+
* pin the minor version and read the CHANGELOG before upgrading.
|
|
17
|
+
*
|
|
18
|
+
* The package's main `src/index.ts` re-exports only the stable surface —
|
|
19
|
+
* @internal items are reachable only via deep import (`thread-phase/agent`
|
|
20
|
+
* is intentionally NOT a configured package subpath).
|
|
21
|
+
*/
|
|
22
|
+
export { runAgentWithTools } from './runner.js';
|
|
23
|
+
export { parseJSON } from './parse-json.js';
|
|
24
|
+
export type { AgentConfig, ActivityEntry, AgentRunResult, AgentRunnerOptions, AgentStreamEvent, FinishReason, UsageInfo, } from './types.js';
|
|
25
|
+
export { toOpenAIMessages, toOpenAITools } from './openai-adapter.js';
|
|
26
|
+
export { consumeStream, looksLikeToolCallText, normalizeFinishReason, type AccumulatedRound, } from './stream-consumer.js';
|
|
27
|
+
export { isRetryableError, isAbortError } from './retry.js';
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,YAAY,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,gBAAgB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent module barrel.
|
|
3
|
+
*
|
|
4
|
+
* Two tiers of exports:
|
|
5
|
+
*
|
|
6
|
+
* 1. Stable (covered by semver, won't break in minor/patch releases):
|
|
7
|
+
* `runAgentWithTools`, `parseJSON`, and the type surface
|
|
8
|
+
* (`AgentConfig`, `AgentRunResult`, `AgentRunnerOptions`,
|
|
9
|
+
* `AgentStreamEvent`, `FinishReason`, `UsageInfo`, `ActivityEntry`).
|
|
10
|
+
*
|
|
11
|
+
* 2. @internal (exported for advanced callers, NOT covered by semver):
|
|
12
|
+
* `consumeStream`, `looksLikeToolCallText`, `normalizeFinishReason`,
|
|
13
|
+
* `AccumulatedRound`, `toOpenAIMessages`, `toOpenAITools`,
|
|
14
|
+
* `isRetryableError`, `isAbortError`. These exist so callers building
|
|
15
|
+
* their own non-loop flows can reuse the building blocks; if you do,
|
|
16
|
+
* pin the minor version and read the CHANGELOG before upgrading.
|
|
17
|
+
*
|
|
18
|
+
* The package's main `src/index.ts` re-exports only the stable surface —
|
|
19
|
+
* @internal items are reachable only via deep import (`thread-phase/agent`
|
|
20
|
+
* is intentionally NOT a configured package subpath).
|
|
21
|
+
*/
|
|
22
|
+
export { runAgentWithTools } from './runner.js';
|
|
23
|
+
export { parseJSON } from './parse-json.js';
|
|
24
|
+
// @internal — see file header. Lower-level pieces for advanced callers.
|
|
25
|
+
export { toOpenAIMessages, toOpenAITools } from './openai-adapter.js';
|
|
26
|
+
export { consumeStream, looksLikeToolCallText, normalizeFinishReason, } from './stream-consumer.js';
|
|
27
|
+
export { isRetryableError, isAbortError } from './retry.js';
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAY5C,wEAAwE;AACxE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,qBAAqB,GAEtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation between thread-phase's internal Message shape and the OpenAI
|
|
3
|
+
* chat-completions wire format. Lives at the single boundary so the rest of
|
|
4
|
+
* the framework stays SDK-agnostic.
|
|
5
|
+
*
|
|
6
|
+
* @internal — both functions are exported for advanced callers (e.g. those
|
|
7
|
+
* driving a custom inference loop) but are not part of the v1 stable
|
|
8
|
+
* surface. They may change between minor versions if the OpenAI SDK
|
|
9
|
+
* evolves.
|
|
10
|
+
*/
|
|
11
|
+
import type { ChatCompletionMessageParam, ChatCompletionTool } from 'openai/resources/chat/completions.js';
|
|
12
|
+
import type { Message, ToolDefinition } from '../messages.js';
|
|
13
|
+
export declare function toOpenAIMessages(systemPrompt: string, messages: Message[]): ChatCompletionMessageParam[];
|
|
14
|
+
export declare function toOpenAITools(tools: ToolDefinition[]): ChatCompletionTool[];
|
|
15
|
+
//# sourceMappingURL=openai-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-adapter.d.ts","sourceRoot":"","sources":["../../src/agent/openai-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,0BAA0B,EAC1B,kBAAkB,EAGnB,MAAM,sCAAsC,CAAC;AAE9C,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE9D,wBAAgB,gBAAgB,CAC9B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,OAAO,EAAE,GAClB,0BAA0B,EAAE,CAkC9B;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,kBAAkB,EAAE,CAS3E"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation between thread-phase's internal Message shape and the OpenAI
|
|
3
|
+
* chat-completions wire format. Lives at the single boundary so the rest of
|
|
4
|
+
* the framework stays SDK-agnostic.
|
|
5
|
+
*
|
|
6
|
+
* @internal — both functions are exported for advanced callers (e.g. those
|
|
7
|
+
* driving a custom inference loop) but are not part of the v1 stable
|
|
8
|
+
* surface. They may change between minor versions if the OpenAI SDK
|
|
9
|
+
* evolves.
|
|
10
|
+
*/
|
|
11
|
+
export function toOpenAIMessages(systemPrompt, messages) {
|
|
12
|
+
const result = [{ role: 'system', content: systemPrompt }];
|
|
13
|
+
for (const msg of messages) {
|
|
14
|
+
if (msg.role === 'system') {
|
|
15
|
+
result.push({ role: 'system', content: msg.content });
|
|
16
|
+
}
|
|
17
|
+
else if (msg.role === 'user') {
|
|
18
|
+
result.push({ role: 'user', content: msg.content });
|
|
19
|
+
}
|
|
20
|
+
else if (msg.role === 'assistant') {
|
|
21
|
+
const out = {
|
|
22
|
+
role: 'assistant',
|
|
23
|
+
content: msg.content || null,
|
|
24
|
+
};
|
|
25
|
+
if (msg.toolCalls.length > 0) {
|
|
26
|
+
out.tool_calls = msg.toolCalls.map((tc) => ({
|
|
27
|
+
id: tc.id,
|
|
28
|
+
type: 'function',
|
|
29
|
+
function: {
|
|
30
|
+
name: tc.name,
|
|
31
|
+
arguments: JSON.stringify(tc.input),
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
result.push(out);
|
|
36
|
+
}
|
|
37
|
+
else if (msg.role === 'tool') {
|
|
38
|
+
result.push({
|
|
39
|
+
role: 'tool',
|
|
40
|
+
tool_call_id: msg.toolCallId,
|
|
41
|
+
content: msg.content,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
export function toOpenAITools(tools) {
|
|
48
|
+
return tools.map((t) => ({
|
|
49
|
+
type: 'function',
|
|
50
|
+
function: {
|
|
51
|
+
name: t.name,
|
|
52
|
+
description: t.description,
|
|
53
|
+
parameters: t.inputSchema,
|
|
54
|
+
},
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=openai-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-adapter.js","sourceRoot":"","sources":["../../src/agent/openai-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,MAAM,UAAU,gBAAgB,CAC9B,YAAoB,EACpB,QAAmB;IAEnB,MAAM,MAAM,GAAiC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;IACzF,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACxD,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACtD,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACpC,MAAM,GAAG,GAAwC;gBAC/C,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI;aAC7B,CAAC;YACF,IAAI,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC7B,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,CAChC,CAAC,EAAE,EAAiC,EAAE,CAAC,CAAC;oBACtC,EAAE,EAAE,EAAE,CAAC,EAAE;oBACT,IAAI,EAAE,UAAU;oBAChB,QAAQ,EAAE;wBACR,IAAI,EAAE,EAAE,CAAC,IAAI;wBACb,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC;qBACpC;iBACF,CAAC,CACH,CAAC;YACJ,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;aAAM,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,MAAM;gBACZ,YAAY,EAAE,GAAG,CAAC,UAAU;gBAC5B,OAAO,EAAE,GAAG,CAAC,OAAO;aACrB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAuB;IACnD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACvB,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,UAAU,EAAE,CAAC,CAAC,WAAW;SAC1B;KACF,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tolerant JSON parse — strips markdown code fences, falls back to extracting
|
|
3
|
+
* the first {...} object from surrounding prose. Returns the supplied fallback
|
|
4
|
+
* on parse failure.
|
|
5
|
+
*
|
|
6
|
+
* Note on the silent-fallback behavior: when the agent's output was truncated
|
|
7
|
+
* (i.e. `AgentRunResult.finishReason === 'length'`), this almost always
|
|
8
|
+
* fails to parse. Callers should branch on `finishReason` BEFORE trusting
|
|
9
|
+
* the parsed value — otherwise truncation is invisible.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseJSON<T>(text: string, fallback: T, onError?: (preview: string, err: Error) => void): T;
|
|
12
|
+
//# sourceMappingURL=parse-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-json.d.ts","sourceRoot":"","sources":["../../src/agent/parse-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,wBAAgB,SAAS,CAAC,CAAC,EACzB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,CAAC,EACX,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,KAAK,IAAI,GAC9C,CAAC,CAqBH"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tolerant JSON parse — strips markdown code fences, falls back to extracting
|
|
3
|
+
* the first {...} object from surrounding prose. Returns the supplied fallback
|
|
4
|
+
* on parse failure.
|
|
5
|
+
*
|
|
6
|
+
* Note on the silent-fallback behavior: when the agent's output was truncated
|
|
7
|
+
* (i.e. `AgentRunResult.finishReason === 'length'`), this almost always
|
|
8
|
+
* fails to parse. Callers should branch on `finishReason` BEFORE trusting
|
|
9
|
+
* the parsed value — otherwise truncation is invisible.
|
|
10
|
+
*/
|
|
11
|
+
export function parseJSON(text, fallback, onError) {
|
|
12
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
13
|
+
const braced = text.match(/(\{[\s\S]*\})/);
|
|
14
|
+
const jsonStr = fenced ? fenced[1] : braced ? braced[1] : text;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(jsonStr.trim());
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const preview = text.slice(0, 200);
|
|
20
|
+
const errObj = err instanceof Error ? err : new Error(String(err));
|
|
21
|
+
if (onError) {
|
|
22
|
+
onError(preview, errObj);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// eslint-disable-next-line no-console
|
|
26
|
+
console.warn(`[parseJSON] failed to parse agent output, using fallback. Preview: "${preview}..."`, err);
|
|
27
|
+
}
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=parse-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-json.js","sourceRoot":"","sources":["../../src/agent/parse-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,QAAW,EACX,OAA+C;IAE/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAEjE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAM,CAAC;IACzC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACnE,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,sCAAsC;YACtC,OAAO,CAAC,IAAI,CACV,uEAAuE,OAAO,MAAM,EACpF,GAAG,CACJ,CAAC;QACJ,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classification for the agent loop. Two questions:
|
|
3
|
+
* - is this error retryable (transient network / overloaded backend)?
|
|
4
|
+
* - is this error a cancellation we should respect rather than retry?
|
|
5
|
+
*
|
|
6
|
+
* Tuned for OpenAI-compat endpoints (vLLM, OpenAI, Ollama) — they all use
|
|
7
|
+
* roughly the same surface for transient failures.
|
|
8
|
+
*
|
|
9
|
+
* @internal — both predicates are exported for advanced callers wrapping
|
|
10
|
+
* the runner with their own retry/abort logic, but they are not part of
|
|
11
|
+
* the v1 stable surface. They may change as we discover new failure modes.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRetryableError(err: unknown): boolean;
|
|
14
|
+
export declare function isAbortError(err: unknown): boolean;
|
|
15
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../src/agent/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAkBtD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAGlD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error classification for the agent loop. Two questions:
|
|
3
|
+
* - is this error retryable (transient network / overloaded backend)?
|
|
4
|
+
* - is this error a cancellation we should respect rather than retry?
|
|
5
|
+
*
|
|
6
|
+
* Tuned for OpenAI-compat endpoints (vLLM, OpenAI, Ollama) — they all use
|
|
7
|
+
* roughly the same surface for transient failures.
|
|
8
|
+
*
|
|
9
|
+
* @internal — both predicates are exported for advanced callers wrapping
|
|
10
|
+
* the runner with their own retry/abort logic, but they are not part of
|
|
11
|
+
* the v1 stable surface. They may change as we discover new failure modes.
|
|
12
|
+
*/
|
|
13
|
+
export function isRetryableError(err) {
|
|
14
|
+
const e = err;
|
|
15
|
+
// Never retry on cancellation — the caller asked us to stop.
|
|
16
|
+
if (e?.name === 'AbortError')
|
|
17
|
+
return false;
|
|
18
|
+
const message = e?.message ?? '';
|
|
19
|
+
const status = e?.status ?? e?.statusCode ?? 0;
|
|
20
|
+
return (status === 429 ||
|
|
21
|
+
status === 500 ||
|
|
22
|
+
status === 502 ||
|
|
23
|
+
status === 503 ||
|
|
24
|
+
status === 504 ||
|
|
25
|
+
message.includes('timeout') ||
|
|
26
|
+
message.includes('ECONNRESET') ||
|
|
27
|
+
message.includes('ECONNREFUSED') ||
|
|
28
|
+
message.includes('overloaded') ||
|
|
29
|
+
message.includes('rate_limit'));
|
|
30
|
+
}
|
|
31
|
+
export function isAbortError(err) {
|
|
32
|
+
const e = err;
|
|
33
|
+
return e?.name === 'AbortError' || (e?.message ?? '').includes('aborted');
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../src/agent/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,MAAM,CAAC,GAAG,GAAuF,CAAC;IAClG,6DAA6D;IAC7D,IAAI,CAAC,EAAE,IAAI,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC,EAAE,UAAU,IAAI,CAAC,CAAC;IAC/C,OAAO,CACL,MAAM,KAAK,GAAG;QACd,MAAM,KAAK,GAAG;QACd,MAAM,KAAK,GAAG;QACd,MAAM,KAAK,GAAG;QACd,MAAM,KAAK,GAAG;QACd,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;QAChC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAC/B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,MAAM,CAAC,GAAG,GAAiD,CAAC;IAC5D,OAAO,CAAC,EAAE,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent runner — the tool-use loop primitive.
|
|
3
|
+
*
|
|
4
|
+
* Given an agent config (system prompt, tools, model tier), a starting
|
|
5
|
+
* conversation, and an executor for the tools, runs an iterated tool-use
|
|
6
|
+
* loop against an OpenAI-compatible inference endpoint until the agent
|
|
7
|
+
* produces final text or hits its round budget.
|
|
8
|
+
*
|
|
9
|
+
* Composed from focused helpers in this directory:
|
|
10
|
+
* - `./types.ts` — public surface
|
|
11
|
+
* - `./openai-adapter.ts` — Message↔OpenAI wire-format translation
|
|
12
|
+
* - `./stream-consumer.ts` — folds streaming chunks into one round's state
|
|
13
|
+
* - `./retry.ts` — error classification (retryable, abort)
|
|
14
|
+
*
|
|
15
|
+
* What the loop owns:
|
|
16
|
+
* - Round budgeting and the compress / hard-stop transitions
|
|
17
|
+
* - Streaming the request, dispatching tools, collecting results
|
|
18
|
+
* - Cumulative usage / executedToolCalls accounting across rounds
|
|
19
|
+
* - Cancellation observation (AbortSignal in options)
|
|
20
|
+
* - The verifyResult hook, the parser-mismatch warning, the retry loop
|
|
21
|
+
*/
|
|
22
|
+
import type { Message } from '../messages.js';
|
|
23
|
+
import type { AgentConfig, AgentRunnerOptions, AgentRunResult } from './types.js';
|
|
24
|
+
export declare function runAgentWithTools(config: AgentConfig, initialMessages: Message[], options: AgentRunnerOptions, agentLabel?: string): Promise<AgentRunResult>;
|
|
25
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,OAAO,KAAK,EACV,OAAO,EAGR,MAAM,gBAAgB,CAAC;AAaxB,OAAO,KAAK,EAEV,WAAW,EACX,kBAAkB,EAClB,cAAc,EAGf,MAAM,YAAY,CAAC;AAKpB,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,EACnB,eAAe,EAAE,OAAO,EAAE,EAC1B,OAAO,EAAE,kBAAkB,EAC3B,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,CAAC,CAkSzB"}
|