zoe-agent 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +154 -0
- package/LICENSE +96 -0
- package/README.md +568 -0
- package/dist/adapters/cli/agent.d.ts +59 -0
- package/dist/adapters/cli/agent.js +232 -0
- package/dist/adapters/cli/bootstrap.d.ts +25 -0
- package/dist/adapters/cli/bootstrap.js +204 -0
- package/dist/adapters/cli/commands/build-registry.d.ts +14 -0
- package/dist/adapters/cli/commands/build-registry.js +88 -0
- package/dist/adapters/cli/commands/clear.d.ts +7 -0
- package/dist/adapters/cli/commands/clear.js +10 -0
- package/dist/adapters/cli/commands/compact.d.ts +13 -0
- package/dist/adapters/cli/commands/compact.js +96 -0
- package/dist/adapters/cli/commands/exit.d.ts +7 -0
- package/dist/adapters/cli/commands/exit.js +9 -0
- package/dist/adapters/cli/commands/gateway.d.ts +7 -0
- package/dist/adapters/cli/commands/gateway.js +152 -0
- package/dist/adapters/cli/commands/help.d.ts +9 -0
- package/dist/adapters/cli/commands/help.js +12 -0
- package/dist/adapters/cli/commands/models.d.ts +10 -0
- package/dist/adapters/cli/commands/models.js +32 -0
- package/dist/adapters/cli/commands/registry.d.ts +70 -0
- package/dist/adapters/cli/commands/registry.js +111 -0
- package/dist/adapters/cli/commands/settings-utils.d.ts +38 -0
- package/dist/adapters/cli/commands/settings-utils.js +182 -0
- package/dist/adapters/cli/commands/settings.d.ts +9 -0
- package/dist/adapters/cli/commands/settings.js +395 -0
- package/dist/adapters/cli/commands/skills.d.ts +7 -0
- package/dist/adapters/cli/commands/skills.js +21 -0
- package/dist/adapters/cli/config-loader.d.ts +27 -0
- package/dist/adapters/cli/config-loader.js +48 -0
- package/dist/adapters/cli/docker-utils.d.ts +37 -0
- package/dist/adapters/cli/docker-utils.js +90 -0
- package/dist/adapters/cli/index.d.ts +2 -0
- package/dist/adapters/cli/index.js +88 -0
- package/dist/adapters/cli/repl.d.ts +22 -0
- package/dist/adapters/cli/repl.js +256 -0
- package/dist/adapters/cli/setup.d.ts +19 -0
- package/dist/adapters/cli/setup.js +613 -0
- package/dist/adapters/cli/system-prompts.d.ts +56 -0
- package/dist/adapters/cli/system-prompts.js +131 -0
- package/dist/adapters/cli/tui/app.d.ts +58 -0
- package/dist/adapters/cli/tui/app.js +314 -0
- package/dist/adapters/cli/tui/components/assistant-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/assistant-message.js +9 -0
- package/dist/adapters/cli/tui/components/autocomplete.d.ts +19 -0
- package/dist/adapters/cli/tui/components/autocomplete.js +75 -0
- package/dist/adapters/cli/tui/components/command-palette.d.ts +15 -0
- package/dist/adapters/cli/tui/components/command-palette.js +50 -0
- package/dist/adapters/cli/tui/components/diff-viewer.d.ts +5 -0
- package/dist/adapters/cli/tui/components/diff-viewer.js +109 -0
- package/dist/adapters/cli/tui/components/error-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/error-message.js +8 -0
- package/dist/adapters/cli/tui/components/footer.d.ts +20 -0
- package/dist/adapters/cli/tui/components/footer.js +19 -0
- package/dist/adapters/cli/tui/components/goal-status.d.ts +12 -0
- package/dist/adapters/cli/tui/components/goal-status.js +22 -0
- package/dist/adapters/cli/tui/components/info-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/info-message.js +8 -0
- package/dist/adapters/cli/tui/components/logo-banner.d.ts +7 -0
- package/dist/adapters/cli/tui/components/logo-banner.js +33 -0
- package/dist/adapters/cli/tui/components/markdown.d.ts +9 -0
- package/dist/adapters/cli/tui/components/markdown.js +92 -0
- package/dist/adapters/cli/tui/components/message-area.d.ts +19 -0
- package/dist/adapters/cli/tui/components/message-area.js +55 -0
- package/dist/adapters/cli/tui/components/permission-prompt.d.ts +13 -0
- package/dist/adapters/cli/tui/components/permission-prompt.js +32 -0
- package/dist/adapters/cli/tui/components/prompt-area.d.ts +22 -0
- package/dist/adapters/cli/tui/components/prompt-area.js +68 -0
- package/dist/adapters/cli/tui/components/text-input.d.ts +27 -0
- package/dist/adapters/cli/tui/components/text-input.js +142 -0
- package/dist/adapters/cli/tui/components/tool-call-block.d.ts +11 -0
- package/dist/adapters/cli/tui/components/tool-call-block.js +68 -0
- package/dist/adapters/cli/tui/components/user-message.d.ts +5 -0
- package/dist/adapters/cli/tui/components/user-message.js +8 -0
- package/dist/adapters/cli/tui/diff/file-write-meta.d.ts +11 -0
- package/dist/adapters/cli/tui/diff/file-write-meta.js +11 -0
- package/dist/adapters/cli/tui/diff/line-diff.d.ts +17 -0
- package/dist/adapters/cli/tui/diff/line-diff.js +44 -0
- package/dist/adapters/cli/tui/feed-serializer.d.ts +29 -0
- package/dist/adapters/cli/tui/feed-serializer.js +70 -0
- package/dist/adapters/cli/tui/file-index.d.ts +8 -0
- package/dist/adapters/cli/tui/file-index.js +41 -0
- package/dist/adapters/cli/tui/hooks/use-agent.d.ts +54 -0
- package/dist/adapters/cli/tui/hooks/use-agent.js +177 -0
- package/dist/adapters/cli/tui/hooks/use-feed.d.ts +16 -0
- package/dist/adapters/cli/tui/hooks/use-feed.js +25 -0
- package/dist/adapters/cli/tui/hooks/use-file-watcher.d.ts +10 -0
- package/dist/adapters/cli/tui/hooks/use-file-watcher.js +43 -0
- package/dist/adapters/cli/tui/hooks/use-keybindings.d.ts +16 -0
- package/dist/adapters/cli/tui/hooks/use-keybindings.js +25 -0
- package/dist/adapters/cli/tui/hooks/use-theme.d.ts +8 -0
- package/dist/adapters/cli/tui/hooks/use-theme.js +12 -0
- package/dist/adapters/cli/tui/index.d.ts +19 -0
- package/dist/adapters/cli/tui/index.js +206 -0
- package/dist/adapters/cli/tui/ink-reset.d.ts +29 -0
- package/dist/adapters/cli/tui/ink-reset.js +57 -0
- package/dist/adapters/cli/tui/layout.d.ts +15 -0
- package/dist/adapters/cli/tui/layout.js +15 -0
- package/dist/adapters/cli/tui/logo/gradient.d.ts +11 -0
- package/dist/adapters/cli/tui/logo/gradient.js +31 -0
- package/dist/adapters/cli/tui/overlays/help-dialog.d.ts +4 -0
- package/dist/adapters/cli/tui/overlays/help-dialog.js +26 -0
- package/dist/adapters/cli/tui/overlays/model-selector.d.ts +14 -0
- package/dist/adapters/cli/tui/overlays/model-selector.js +43 -0
- package/dist/adapters/cli/tui/overlays/session-selector.d.ts +35 -0
- package/dist/adapters/cli/tui/overlays/session-selector.js +162 -0
- package/dist/adapters/cli/tui/overlays/settings-overlay.d.ts +24 -0
- package/dist/adapters/cli/tui/overlays/settings-overlay.js +126 -0
- package/dist/adapters/cli/tui/session-export.d.ts +21 -0
- package/dist/adapters/cli/tui/session-export.js +63 -0
- package/dist/adapters/cli/tui/theme.d.ts +23 -0
- package/dist/adapters/cli/tui/theme.js +22 -0
- package/dist/adapters/cli/tui/types.d.ts +52 -0
- package/dist/adapters/cli/tui/types.js +12 -0
- package/dist/adapters/sdk/agent.d.ts +20 -0
- package/dist/adapters/sdk/agent.js +356 -0
- package/dist/adapters/sdk/http.d.ts +43 -0
- package/dist/adapters/sdk/http.js +61 -0
- package/dist/adapters/sdk/index.d.ts +58 -0
- package/dist/adapters/sdk/index.js +209 -0
- package/dist/adapters/sdk/settings.d.ts +18 -0
- package/dist/adapters/sdk/settings.js +57 -0
- package/dist/adapters/sdk/tools.d.ts +7 -0
- package/dist/adapters/sdk/tools.js +13 -0
- package/dist/adapters/server/auth.d.ts +53 -0
- package/dist/adapters/server/auth.js +168 -0
- package/dist/adapters/server/index.d.ts +40 -0
- package/dist/adapters/server/index.js +255 -0
- package/dist/adapters/server/rest-gateway.d.ts +13 -0
- package/dist/adapters/server/rest-gateway.js +218 -0
- package/dist/adapters/server/rest.d.ts +37 -0
- package/dist/adapters/server/rest.js +341 -0
- package/dist/adapters/server/server-core.d.ts +55 -0
- package/dist/adapters/server/server-core.js +121 -0
- package/dist/adapters/server/session-store.d.ts +81 -0
- package/dist/adapters/server/session-store.js +272 -0
- package/dist/adapters/server/settings-handlers.d.ts +24 -0
- package/dist/adapters/server/settings-handlers.js +360 -0
- package/dist/adapters/server/standalone.d.ts +19 -0
- package/dist/adapters/server/standalone.js +113 -0
- package/dist/adapters/server/websocket.d.ts +26 -0
- package/dist/adapters/server/websocket.js +68 -0
- package/dist/adapters/server/ws-handlers.d.ts +32 -0
- package/dist/adapters/server/ws-handlers.js +523 -0
- package/dist/adapters/server/ws-types.d.ts +304 -0
- package/dist/adapters/server/ws-types.js +7 -0
- package/dist/core/agent-loop.d.ts +68 -0
- package/dist/core/agent-loop.js +423 -0
- package/dist/core/config.d.ts +115 -0
- package/dist/core/config.js +189 -0
- package/dist/core/errors.d.ts +58 -0
- package/dist/core/errors.js +88 -0
- package/dist/core/hooks.d.ts +35 -0
- package/dist/core/hooks.js +49 -0
- package/dist/core/index.d.ts +23 -0
- package/dist/core/index.js +29 -0
- package/dist/core/message-convert.d.ts +41 -0
- package/dist/core/message-convert.js +94 -0
- package/dist/core/middleware/auth.d.ts +24 -0
- package/dist/core/middleware/auth.js +28 -0
- package/dist/core/middleware/logging.d.ts +23 -0
- package/dist/core/middleware/logging.js +28 -0
- package/dist/core/middleware/rate-limit.d.ts +27 -0
- package/dist/core/middleware/rate-limit.js +38 -0
- package/dist/core/middleware/semantic-tools.d.ts +10 -0
- package/dist/core/middleware/semantic-tools.js +43 -0
- package/dist/core/middleware.d.ts +48 -0
- package/dist/core/middleware.js +38 -0
- package/dist/core/permission.d.ts +25 -0
- package/dist/core/permission.js +50 -0
- package/dist/core/provider-config.d.ts +129 -0
- package/dist/core/provider-config.js +273 -0
- package/dist/core/provider-env.d.ts +39 -0
- package/dist/core/provider-env.js +142 -0
- package/dist/core/provider-resolver.d.ts +12 -0
- package/dist/core/provider-resolver.js +12 -0
- package/dist/core/session-store.d.ts +75 -0
- package/dist/core/session-store.js +245 -0
- package/dist/core/settings-manager.d.ts +57 -0
- package/dist/core/settings-manager.js +359 -0
- package/dist/core/settings-schema.d.ts +38 -0
- package/dist/core/settings-schema.js +171 -0
- package/dist/core/skill-catalog.d.ts +6 -0
- package/dist/core/skill-catalog.js +17 -0
- package/dist/core/skill-invoker.d.ts +127 -0
- package/dist/core/skill-invoker.js +182 -0
- package/dist/core/stream-accumulator.d.ts +21 -0
- package/dist/core/stream-accumulator.js +51 -0
- package/dist/core/stream-manager.d.ts +58 -0
- package/dist/core/stream-manager.js +212 -0
- package/dist/core/tool-executor.d.ts +84 -0
- package/dist/core/tool-executor.js +256 -0
- package/dist/core/types.d.ts +259 -0
- package/dist/core/types.js +11 -0
- package/dist/gateway/gateway.d.ts +52 -0
- package/dist/gateway/gateway.js +537 -0
- package/dist/gateway/index.d.ts +21 -0
- package/dist/gateway/index.js +31 -0
- package/dist/gateway/openapi-importer.d.ts +15 -0
- package/dist/gateway/openapi-importer.js +66 -0
- package/dist/gateway/semantic-scorer.d.ts +7 -0
- package/dist/gateway/semantic-scorer.js +24 -0
- package/dist/gateway/settings-adapter.d.ts +49 -0
- package/dist/gateway/settings-adapter.js +137 -0
- package/dist/gateway/tool-factory.d.ts +9 -0
- package/dist/gateway/tool-factory.js +414 -0
- package/dist/gateway/types.d.ts +68 -0
- package/dist/gateway/types.js +7 -0
- package/dist/models-catalog.js +46 -0
- package/dist/providers/anthropic.d.ts +22 -0
- package/dist/providers/anthropic.js +148 -0
- package/dist/providers/factory.d.ts +10 -0
- package/dist/providers/factory.js +25 -0
- package/dist/providers/openai.d.ts +15 -0
- package/dist/providers/openai.js +71 -0
- package/dist/providers/types.d.ts +48 -0
- package/dist/providers/types.js +1 -0
- package/dist/skills/args.d.ts +37 -0
- package/dist/skills/args.js +99 -0
- package/dist/skills/index.d.ts +11 -0
- package/dist/skills/index.js +23 -0
- package/dist/skills/loader.d.ts +3 -0
- package/dist/skills/loader.js +59 -0
- package/dist/skills/parser.d.ts +7 -0
- package/dist/skills/parser.js +152 -0
- package/dist/skills/registry.d.ts +13 -0
- package/dist/skills/registry.js +74 -0
- package/dist/skills/resolver.d.ts +19 -0
- package/dist/skills/resolver.js +116 -0
- package/dist/skills/types.d.ts +74 -0
- package/dist/skills/types.js +50 -0
- package/dist/tools/browser.d.ts +2 -0
- package/dist/tools/browser.js +68 -0
- package/dist/tools/core.d.ts +20 -0
- package/dist/tools/core.js +244 -0
- package/dist/tools/email.d.ts +2 -0
- package/dist/tools/email.js +61 -0
- package/dist/tools/image.d.ts +2 -0
- package/dist/tools/image.js +257 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +88 -0
- package/dist/tools/interface.d.ts +22 -0
- package/dist/tools/interface.js +1 -0
- package/dist/tools/notify.d.ts +2 -0
- package/dist/tools/notify.js +100 -0
- package/dist/tools/prompt-optimizer.d.ts +2 -0
- package/dist/tools/prompt-optimizer.js +65 -0
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +184 -0
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +78 -0
- package/dist/tools/todos.d.ts +10 -0
- package/dist/tools/todos.js +50 -0
- package/package.json +119 -0
- package/skills/docker-ops/SKILL.md +329 -0
- package/skills/k8s-deploy/SKILL.md +397 -0
- package/skills/log-analyzer/SKILL.md +331 -0
- package/skills/speckit-analyze/SKILL.md +260 -0
- package/skills/speckit-checklist/SKILL.md +374 -0
- package/skills/speckit-clarify/SKILL.md +286 -0
- package/skills/speckit-constitution/SKILL.md +157 -0
- package/skills/speckit-implement/SKILL.md +224 -0
- package/skills/speckit-plan/SKILL.md +171 -0
- package/skills/speckit-specify/SKILL.md +346 -0
- package/skills/speckit-tasks/SKILL.md +215 -0
- package/skills/speckit-taskstoissues/SKILL.md +107 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/** Zoe Core — THE Agent Loop (single implementation) */
|
|
2
|
+
import { generateId, now, toZoeError, messageToProviderMessage, providerToolCallToToolCall } from "./message-convert.js";
|
|
3
|
+
import { StreamingResponseAccumulator } from "./stream-accumulator.js";
|
|
4
|
+
import { executeTool, normalizeToolResult } from "./tool-executor.js";
|
|
5
|
+
import { compose } from "./middleware.js";
|
|
6
|
+
import { checkToolPermission, getToolRiskCategory } from "./permission.js";
|
|
7
|
+
import { getAllToolModules } from "./tool-executor.js";
|
|
8
|
+
import { getModelMeta } from "../models-catalog.js";
|
|
9
|
+
/**
|
|
10
|
+
* Run the Zoe agent loop - THE single implementation.
|
|
11
|
+
*
|
|
12
|
+
* This is the canonical agent loop that all other entry points (createAgent,
|
|
13
|
+
* generateText, streamText, CLI Agent) will delegate to. It handles:
|
|
14
|
+
*
|
|
15
|
+
* - Multi-step reasoning with tool execution
|
|
16
|
+
* - Provider resolution (including per-skill switching via providerFactory)
|
|
17
|
+
* - System prompt injection
|
|
18
|
+
* - Abort signal handling
|
|
19
|
+
* - Hook execution
|
|
20
|
+
* - Usage estimation
|
|
21
|
+
* - Structured error reporting
|
|
22
|
+
* - Middleware pipeline (when provided)
|
|
23
|
+
*
|
|
24
|
+
* @param options - Agent loop configuration
|
|
25
|
+
* @returns AgentLoopResult with messages, steps, tool calls, usage, and finish reason
|
|
26
|
+
*/
|
|
27
|
+
export async function runAgentLoop(options) {
|
|
28
|
+
const { provider, model, messages, toolDefs, systemPrompt, skillCatalog, maxSteps, hooks, signal, config = {}, metadata = {}, onStep, providerFactory, middleware, } = options;
|
|
29
|
+
// ── No middleware: run loop directly (backward compatible) ────────────
|
|
30
|
+
if (!middleware || middleware.length === 0) {
|
|
31
|
+
return executeLoop(options);
|
|
32
|
+
}
|
|
33
|
+
// ── With middleware: wrap loop in pipeline ────────────────────────────
|
|
34
|
+
const ctx = {
|
|
35
|
+
requestId: generateId(),
|
|
36
|
+
messages,
|
|
37
|
+
provider,
|
|
38
|
+
model,
|
|
39
|
+
toolDefs,
|
|
40
|
+
metadata,
|
|
41
|
+
signal,
|
|
42
|
+
startedAt: Date.now(),
|
|
43
|
+
};
|
|
44
|
+
try {
|
|
45
|
+
await compose(middleware)(ctx, async () => {
|
|
46
|
+
// Rebuild options from ctx to capture middleware mutations (e.g., injected tools)
|
|
47
|
+
const mergedOptions = {
|
|
48
|
+
...options,
|
|
49
|
+
toolDefs: ctx.toolDefs,
|
|
50
|
+
config: {
|
|
51
|
+
...options.config,
|
|
52
|
+
agentName: options.config?.agentName ?? 'zoe',
|
|
53
|
+
...(ctx.metadata.injectedTools ? { injectedTools: ctx.metadata.injectedTools } : {}),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const result = await executeLoop(mergedOptions);
|
|
57
|
+
ctx.result = {
|
|
58
|
+
messages: result.messages,
|
|
59
|
+
steps: result.steps,
|
|
60
|
+
toolCalls: result.toolCalls,
|
|
61
|
+
usage: result.usage,
|
|
62
|
+
finishReason: result.finishReason,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
// ctx.result is populated by the final handler
|
|
66
|
+
if (ctx.result) {
|
|
67
|
+
return {
|
|
68
|
+
messages: ctx.result.messages,
|
|
69
|
+
steps: ctx.result.steps,
|
|
70
|
+
toolCalls: ctx.result.toolCalls,
|
|
71
|
+
usage: ctx.result.usage,
|
|
72
|
+
finishReason: ctx.result.finishReason,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Middleware completed without populating result (shouldn't happen)
|
|
76
|
+
return {
|
|
77
|
+
messages,
|
|
78
|
+
steps: [],
|
|
79
|
+
toolCalls: [],
|
|
80
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0 },
|
|
81
|
+
finishReason: "error",
|
|
82
|
+
error: {
|
|
83
|
+
message: "Middleware completed without producing a result",
|
|
84
|
+
code: "MIDDLEWARE_ERROR",
|
|
85
|
+
retryable: false,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
// Log the error for audit trail even though middleware chain was interrupted
|
|
91
|
+
console.error(`[middleware] request ${ctx.requestId} failed after ${Date.now() - ctx.startedAt}ms:`, err instanceof Error ? err.message : String(err));
|
|
92
|
+
return {
|
|
93
|
+
messages,
|
|
94
|
+
steps: [],
|
|
95
|
+
toolCalls: [],
|
|
96
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0, cost: 0 },
|
|
97
|
+
finishReason: "error",
|
|
98
|
+
error: {
|
|
99
|
+
message: err instanceof Error ? err.message : String(err),
|
|
100
|
+
code: err?.code ?? "MIDDLEWARE_ERROR",
|
|
101
|
+
retryable: false,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Execute the core agent loop (no middleware wrapping).
|
|
108
|
+
* Extracted from runAgentLoop for clarity.
|
|
109
|
+
*/
|
|
110
|
+
async function executeLoop(options) {
|
|
111
|
+
const { provider, model, messages, toolDefs, systemPrompt, skillCatalog, maxSteps, hooks, signal, config = {}, onStep, stream, providerFactory, } = options;
|
|
112
|
+
// Destructure approveTool outside the loop for closure access
|
|
113
|
+
const approveTool = options.approveTool;
|
|
114
|
+
const permissionLevel = options.permissionLevel;
|
|
115
|
+
const autoConfirm = options.autoConfirm;
|
|
116
|
+
// Prepend system prompt if provided and messages[0] is not already a system message
|
|
117
|
+
if (systemPrompt && messages.length > 0 && messages[0].role !== "system") {
|
|
118
|
+
messages.unshift({
|
|
119
|
+
id: generateId(),
|
|
120
|
+
role: "system",
|
|
121
|
+
content: systemPrompt,
|
|
122
|
+
timestamp: now(),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// Append skill catalog to existing system message
|
|
126
|
+
if (skillCatalog && messages.length > 0 && messages[0].role === 'system') {
|
|
127
|
+
messages[0] = { ...messages[0], content: messages[0].content + '\n\n' + skillCatalog };
|
|
128
|
+
}
|
|
129
|
+
const steps = [];
|
|
130
|
+
const allToolCalls = [];
|
|
131
|
+
let finishReason = "stop";
|
|
132
|
+
let loopError;
|
|
133
|
+
// For usage calculation
|
|
134
|
+
let totalPromptChars = 0;
|
|
135
|
+
let totalCompletionChars = 0;
|
|
136
|
+
// Track current provider (may change per step if providerFactory is used)
|
|
137
|
+
let currentProvider = provider;
|
|
138
|
+
let currentModel = model;
|
|
139
|
+
// Track whether the loop exhausted maxSteps
|
|
140
|
+
let hitMaxSteps = false;
|
|
141
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
142
|
+
try {
|
|
143
|
+
// Check abort
|
|
144
|
+
if (signal?.aborted) {
|
|
145
|
+
finishReason = "aborted";
|
|
146
|
+
loopError = {
|
|
147
|
+
message: "Operation was aborted",
|
|
148
|
+
code: "ABORTED",
|
|
149
|
+
retryable: false,
|
|
150
|
+
};
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
// Resolve provider for this step (for skill-driven provider switching)
|
|
154
|
+
if (providerFactory) {
|
|
155
|
+
try {
|
|
156
|
+
const resolved = await providerFactory.resolve();
|
|
157
|
+
currentProvider = resolved.provider;
|
|
158
|
+
currentModel = resolved.model;
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
finishReason = "error";
|
|
162
|
+
loopError = {
|
|
163
|
+
message: err instanceof Error ? err.message : String(err),
|
|
164
|
+
code: "PROVIDER_ERROR",
|
|
165
|
+
retryable: true,
|
|
166
|
+
provider: currentModel,
|
|
167
|
+
};
|
|
168
|
+
const zoeErr = toZoeError(err, "PROVIDER_ERROR");
|
|
169
|
+
await hooks.onError(zoeErr);
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Convert messages to provider format
|
|
174
|
+
const providerMessages = messages.map(messageToProviderMessage);
|
|
175
|
+
// Call provider (stream if available, else chat). Streaming emits
|
|
176
|
+
// text_delta steps as tokens arrive; non-streaming emits one complete
|
|
177
|
+
// 'text' step below. Tool calls are reassembled by the accumulator.
|
|
178
|
+
let response;
|
|
179
|
+
let streamed = false;
|
|
180
|
+
try {
|
|
181
|
+
if (stream && typeof currentProvider.chatStream === 'function') {
|
|
182
|
+
streamed = true;
|
|
183
|
+
const acc = new StreamingResponseAccumulator();
|
|
184
|
+
for await (const delta of currentProvider.chatStream(providerMessages, toolDefs, { signal })) {
|
|
185
|
+
if (delta.type === 'text_delta' && delta.content) {
|
|
186
|
+
acc.appendText(delta.content);
|
|
187
|
+
const deltaStep = { type: 'text_delta', content: delta.content, timestamp: now() };
|
|
188
|
+
steps.push(deltaStep);
|
|
189
|
+
await hooks.onStep(deltaStep);
|
|
190
|
+
if (onStep)
|
|
191
|
+
onStep(deltaStep);
|
|
192
|
+
}
|
|
193
|
+
else if (delta.type === 'tool_call_begin') {
|
|
194
|
+
acc.beginToolCall(delta.index, delta.id, delta.name);
|
|
195
|
+
}
|
|
196
|
+
else if (delta.type === 'tool_call_delta') {
|
|
197
|
+
acc.appendToolCallArgs(delta.index, delta.argumentsDelta);
|
|
198
|
+
}
|
|
199
|
+
else if (delta.type === 'finish' && delta.usage) {
|
|
200
|
+
acc.setUsage(delta.usage);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
response = acc.toResponse();
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
response = await currentProvider.chat(providerMessages, toolDefs, { signal });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
finishReason = "error";
|
|
211
|
+
const zoeErr = toZoeError(err, "PROVIDER_ERROR");
|
|
212
|
+
loopError = {
|
|
213
|
+
message: zoeErr.message,
|
|
214
|
+
code: "PROVIDER_ERROR",
|
|
215
|
+
retryable: zoeErr.retryable,
|
|
216
|
+
provider: currentModel,
|
|
217
|
+
};
|
|
218
|
+
await hooks.onError(zoeErr);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
// Track prompt chars for usage
|
|
222
|
+
for (const msg of providerMessages) {
|
|
223
|
+
totalPromptChars += (msg.content ?? "").length;
|
|
224
|
+
}
|
|
225
|
+
// Text content. When streamed, tokens already went out as text_delta steps,
|
|
226
|
+
// so we only emit the complete 'text' step for the non-streamed path; the
|
|
227
|
+
// assembled content is always added to history either way.
|
|
228
|
+
if (response.content) {
|
|
229
|
+
totalCompletionChars += response.content.length;
|
|
230
|
+
if (!streamed) {
|
|
231
|
+
const textStep = {
|
|
232
|
+
type: "text",
|
|
233
|
+
content: response.content,
|
|
234
|
+
timestamp: now(),
|
|
235
|
+
};
|
|
236
|
+
steps.push(textStep);
|
|
237
|
+
await hooks.onStep(textStep);
|
|
238
|
+
if (onStep)
|
|
239
|
+
onStep(textStep);
|
|
240
|
+
}
|
|
241
|
+
// Add assistant message with text content
|
|
242
|
+
messages.push({
|
|
243
|
+
id: generateId(),
|
|
244
|
+
role: "assistant",
|
|
245
|
+
content: response.content,
|
|
246
|
+
timestamp: now(),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// Tool calls
|
|
250
|
+
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
251
|
+
const assistantToolCalls = response.tool_calls.map(providerToolCallToToolCall);
|
|
252
|
+
allToolCalls.push(...assistantToolCalls);
|
|
253
|
+
// Add assistant message with tool calls
|
|
254
|
+
const assistantMsg = {
|
|
255
|
+
id: generateId(),
|
|
256
|
+
role: "assistant",
|
|
257
|
+
content: response.content ?? "",
|
|
258
|
+
toolCalls: assistantToolCalls,
|
|
259
|
+
timestamp: now(),
|
|
260
|
+
};
|
|
261
|
+
messages.push(assistantMsg);
|
|
262
|
+
// Execute each tool call
|
|
263
|
+
for (const tc of response.tool_calls) {
|
|
264
|
+
if (signal?.aborted) {
|
|
265
|
+
finishReason = "aborted";
|
|
266
|
+
loopError = {
|
|
267
|
+
message: "Operation was aborted during tool execution",
|
|
268
|
+
code: "ABORTED",
|
|
269
|
+
retryable: false,
|
|
270
|
+
};
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
let parsedArgs;
|
|
274
|
+
try {
|
|
275
|
+
parsedArgs = JSON.parse(tc.arguments);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
parsedArgs = { raw: tc.arguments };
|
|
279
|
+
}
|
|
280
|
+
await hooks.beforeToolCall({ name: tc.name, args: parsedArgs });
|
|
281
|
+
// Forward a tool's live progress (e.g. streaming shell stdout) to the
|
|
282
|
+
// adapter as a tool_progress step. Emitted via onStep only — not pushed
|
|
283
|
+
// to result.steps (chunks are transient presentation, not semantic).
|
|
284
|
+
const onUpdate = (progress) => {
|
|
285
|
+
if (progress.message != null && onStep) {
|
|
286
|
+
onStep({
|
|
287
|
+
type: "tool_progress",
|
|
288
|
+
toolCallId: tc.id,
|
|
289
|
+
name: tc.name,
|
|
290
|
+
args: parsedArgs,
|
|
291
|
+
content: progress.message,
|
|
292
|
+
timestamp: now(),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
const execExtra = { onUpdate, signal };
|
|
297
|
+
// Check for dynamically injected tools (from semantic middleware)
|
|
298
|
+
const injectedTools = config?.injectedTools;
|
|
299
|
+
const injectedModule = injectedTools instanceof Map ? injectedTools.get(tc.name) : undefined;
|
|
300
|
+
const start = now();
|
|
301
|
+
let output;
|
|
302
|
+
let metadata;
|
|
303
|
+
// Runs the tool (injected module or registry), normalizing both branches
|
|
304
|
+
// into { output, metadata } and turning throws into an error output.
|
|
305
|
+
// Shared by all three permission paths below so the try/catch lives once.
|
|
306
|
+
const runToolSafely = async () => {
|
|
307
|
+
try {
|
|
308
|
+
const result = injectedModule
|
|
309
|
+
? normalizeToolResult(await injectedModule.handler(parsedArgs, config))
|
|
310
|
+
: await executeTool(tc.name, parsedArgs, config, execExtra);
|
|
311
|
+
return { output: result.output, metadata: result.metadata };
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
return { output: `Error: ${err instanceof Error ? err.message : String(err)}` };
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
// Permission pre-filter + adapter-level tool approval
|
|
318
|
+
const effectiveLevel = permissionLevel ?? "moderate";
|
|
319
|
+
if (autoConfirm) {
|
|
320
|
+
// --headless mode: bypass permission matrix, auto-approve everything
|
|
321
|
+
({ output, metadata } = await runToolSafely());
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
const riskCategory = injectedModule?.risk
|
|
325
|
+
?? getToolRiskCategory(tc.name, getAllToolModules());
|
|
326
|
+
const decision = checkToolPermission(effectiveLevel, riskCategory);
|
|
327
|
+
if (decision === "auto") {
|
|
328
|
+
({ output, metadata } = await runToolSafely());
|
|
329
|
+
}
|
|
330
|
+
else if (approveTool) {
|
|
331
|
+
let approved;
|
|
332
|
+
try {
|
|
333
|
+
approved = await approveTool({ name: tc.name, args: parsedArgs });
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
approved = false;
|
|
337
|
+
}
|
|
338
|
+
if (!approved) {
|
|
339
|
+
output = "User denied tool execution.";
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
({ output, metadata } = await runToolSafely());
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
output = "Tool execution denied.";
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const duration = now() - start;
|
|
350
|
+
// Note: tool output chars are NOT counted here because they will be
|
|
351
|
+
// counted as promptChars on the next loop iteration when the message
|
|
352
|
+
// history (including tool results) is sent to the provider.
|
|
353
|
+
// Add tool result message
|
|
354
|
+
messages.push({
|
|
355
|
+
id: generateId(),
|
|
356
|
+
role: "tool",
|
|
357
|
+
content: output,
|
|
358
|
+
toolCallId: tc.id,
|
|
359
|
+
timestamp: now(),
|
|
360
|
+
});
|
|
361
|
+
// Record step
|
|
362
|
+
const toolStep = {
|
|
363
|
+
type: "tool_call",
|
|
364
|
+
toolCall: {
|
|
365
|
+
id: tc.id,
|
|
366
|
+
name: tc.name,
|
|
367
|
+
args: parsedArgs,
|
|
368
|
+
result: output,
|
|
369
|
+
duration,
|
|
370
|
+
},
|
|
371
|
+
metadata,
|
|
372
|
+
timestamp: now(),
|
|
373
|
+
};
|
|
374
|
+
steps.push(toolStep);
|
|
375
|
+
await hooks.onStep(toolStep);
|
|
376
|
+
await hooks.afterToolCall({ name: tc.name, output, duration });
|
|
377
|
+
if (onStep)
|
|
378
|
+
onStep(toolStep);
|
|
379
|
+
}
|
|
380
|
+
if (finishReason === "aborted")
|
|
381
|
+
break;
|
|
382
|
+
// Continue the loop to get the next response
|
|
383
|
+
// Mark if this was the last allowed iteration
|
|
384
|
+
if (step + 1 >= maxSteps) {
|
|
385
|
+
hitMaxSteps = true;
|
|
386
|
+
}
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
// No tool calls — we're done
|
|
390
|
+
finishReason = "stop";
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
if (providerFactory)
|
|
395
|
+
providerFactory.restore();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// The loop ran all iterations with tool calls on the last one
|
|
399
|
+
if (hitMaxSteps) {
|
|
400
|
+
finishReason = "max_steps";
|
|
401
|
+
}
|
|
402
|
+
// Calculate usage
|
|
403
|
+
const promptTokens = Math.ceil(totalPromptChars / 4);
|
|
404
|
+
const completionTokens = Math.ceil(totalCompletionChars / 4);
|
|
405
|
+
const pricing = getModelMeta(currentModel)?.pricing;
|
|
406
|
+
const cost = pricing
|
|
407
|
+
? (promptTokens * pricing.input + completionTokens * pricing.output) / 1_000_000
|
|
408
|
+
: 0;
|
|
409
|
+
const usage = {
|
|
410
|
+
promptTokens,
|
|
411
|
+
completionTokens,
|
|
412
|
+
totalTokens: promptTokens + completionTokens,
|
|
413
|
+
cost,
|
|
414
|
+
};
|
|
415
|
+
return {
|
|
416
|
+
messages,
|
|
417
|
+
steps,
|
|
418
|
+
toolCalls: allToolCalls,
|
|
419
|
+
usage,
|
|
420
|
+
finishReason,
|
|
421
|
+
error: loopError,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zoe Core — Config Utilities
|
|
3
|
+
*
|
|
4
|
+
* Config loading, merging, and environment overrides.
|
|
5
|
+
* Chalk-free — suitable for all adapters (CLI, SDK, Server).
|
|
6
|
+
*/
|
|
7
|
+
import { ProviderType } from '../providers/types.js';
|
|
8
|
+
export interface AppConfig {
|
|
9
|
+
provider?: ProviderType;
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
model?: string;
|
|
13
|
+
models?: {
|
|
14
|
+
'openai-compatible'?: {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
model: string;
|
|
18
|
+
};
|
|
19
|
+
openai?: {
|
|
20
|
+
apiKey: string;
|
|
21
|
+
model: string;
|
|
22
|
+
};
|
|
23
|
+
anthropic?: {
|
|
24
|
+
apiKey: string;
|
|
25
|
+
model: string;
|
|
26
|
+
};
|
|
27
|
+
glm?: {
|
|
28
|
+
apiKey: string;
|
|
29
|
+
model: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
imageApiKey?: string;
|
|
33
|
+
imageBaseUrl?: string;
|
|
34
|
+
imageModel?: string;
|
|
35
|
+
imageSize?: string;
|
|
36
|
+
imageQuality?: string;
|
|
37
|
+
imageStyle?: string;
|
|
38
|
+
imageN?: number;
|
|
39
|
+
smtpHost?: string;
|
|
40
|
+
smtpPort?: string;
|
|
41
|
+
smtpUser?: string;
|
|
42
|
+
smtpPass?: string;
|
|
43
|
+
smtpFrom?: string;
|
|
44
|
+
tavilyApiKey?: string;
|
|
45
|
+
autoConfirm?: boolean;
|
|
46
|
+
permissionLevel?: "strict" | "moderate" | "permissive";
|
|
47
|
+
feishuWebhook?: string;
|
|
48
|
+
feishuKeyword?: string;
|
|
49
|
+
dingtalkWebhook?: string;
|
|
50
|
+
dingtalkKeyword?: string;
|
|
51
|
+
wecomWebhook?: string;
|
|
52
|
+
wecomKeyword?: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns the config file path for the given scope.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getConfigPath(global?: boolean): string;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the config directory path for the given scope.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getConfigDir(global?: boolean): string;
|
|
62
|
+
/**
|
|
63
|
+
* Returns both global and local config paths.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getConfigPaths(): {
|
|
66
|
+
global: string;
|
|
67
|
+
local: string;
|
|
68
|
+
globalDir: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Load and parse a JSON config file.
|
|
72
|
+
* Returns `{ config, warning }` — warning is set if parsing failed.
|
|
73
|
+
*/
|
|
74
|
+
export declare function loadJsonConfig(filePath: string): {
|
|
75
|
+
config: AppConfig;
|
|
76
|
+
warning?: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Load global and local configs and merge them.
|
|
80
|
+
* Priority: local > global.
|
|
81
|
+
*/
|
|
82
|
+
export declare function loadMergedConfig(): AppConfig;
|
|
83
|
+
/**
|
|
84
|
+
* Apply environment variable overrides to the merged config.
|
|
85
|
+
* Env vars take priority over JSON config for tool settings.
|
|
86
|
+
* Also injects provider API keys from env vars into the models map.
|
|
87
|
+
*/
|
|
88
|
+
export declare function applyEnvOverrides(config: AppConfig): AppConfig;
|
|
89
|
+
/**
|
|
90
|
+
* Auto-migrate legacy config format (top-level apiKey/baseUrl/model) to the
|
|
91
|
+
* models map format used by the current architecture.
|
|
92
|
+
*/
|
|
93
|
+
export declare function migrateLegacyFormat(config: AppConfig, options?: {
|
|
94
|
+
model?: string;
|
|
95
|
+
}): AppConfig;
|
|
96
|
+
/**
|
|
97
|
+
* Resolve the active provider type from CLI flags, env vars, and config.
|
|
98
|
+
* Checks LLM_PROVIDER env var as a standard alias for ZOE_PROVIDER.
|
|
99
|
+
*/
|
|
100
|
+
export declare function resolveActiveProviderType(config: AppConfig, options?: {
|
|
101
|
+
provider?: string;
|
|
102
|
+
}): ProviderType;
|
|
103
|
+
/**
|
|
104
|
+
* Save config to disk. If a local config exists, saves there; otherwise global.
|
|
105
|
+
*/
|
|
106
|
+
export declare function saveConfig(config: AppConfig): void;
|
|
107
|
+
/**
|
|
108
|
+
* Save config to a specific path.
|
|
109
|
+
* Throws on failure — callers handle error display.
|
|
110
|
+
*/
|
|
111
|
+
export declare function writeConfigToPath(config: AppConfig, targetFile: string): void;
|
|
112
|
+
/**
|
|
113
|
+
* Mask a secret string for display, showing only first 3 and last 4 chars.
|
|
114
|
+
*/
|
|
115
|
+
export declare function maskSecret(secret?: string): string;
|