@cjhyy/code-shell-core 0.5.0-rc.0 → 0.5.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/agent-definition-registry.d.ts +29 -0
- package/dist/agent/agent-definition-registry.js +74 -0
- package/dist/agent/agent-definition.d.ts +37 -0
- package/dist/agent/agent-definition.js +59 -0
- package/dist/arena/arena.js +11 -12
- package/dist/arena/context/context-tools.js +2 -4
- package/dist/arena/context/within-root.d.ts +7 -0
- package/dist/arena/context/within-root.js +15 -0
- package/dist/arena/iterate/phases/argue.js +8 -2
- package/dist/arena/iterate/phases/revise.js +1 -1
- package/dist/arena/iterate/phases/tournament.js +3 -3
- package/dist/arena/phases/adjudication.js +1 -4
- package/dist/arena/phases/build-consensus.js +1 -4
- package/dist/arena/phases/cross-review.js +2 -8
- package/dist/arena/phases/debate-rounds.js +1 -4
- package/dist/arena/phases/participant-research.js +1 -4
- package/dist/arena/phases/planning-detail-expansion.js +8 -10
- package/dist/arena/planner.js +0 -1
- package/dist/arena/providers/docs.d.ts +1 -0
- package/dist/arena/providers/docs.js +5 -2
- package/dist/arena/providers/repo.d.ts +1 -0
- package/dist/arena/providers/repo.js +5 -2
- package/dist/arena/strategies/utils.js +36 -3
- package/dist/arena/transitions.d.ts +7 -2
- package/dist/arena/transitions.js +9 -4
- package/dist/arena/types.d.ts +8 -2
- package/dist/automation/cron-expr.d.ts +31 -0
- package/dist/automation/cron-expr.js +151 -0
- package/dist/automation/index.d.ts +41 -0
- package/dist/automation/index.js +39 -0
- package/dist/automation/runner.d.ts +67 -0
- package/dist/automation/runner.js +56 -0
- package/dist/automation/scheduler.d.ts +156 -0
- package/dist/automation/scheduler.js +546 -0
- package/dist/automation/store.d.ts +39 -0
- package/dist/automation/store.js +119 -0
- package/dist/automation/write-policy.d.ts +34 -0
- package/dist/automation/write-policy.js +113 -0
- package/dist/automation/write-run.d.ts +51 -0
- package/dist/automation/write-run.js +38 -0
- package/dist/capability-control/index.d.ts +5 -0
- package/dist/capability-control/index.js +3 -0
- package/dist/capability-control/overlay.d.ts +49 -0
- package/dist/capability-control/overlay.js +89 -0
- package/dist/capability-control/project.d.ts +65 -0
- package/dist/capability-control/project.js +147 -0
- package/dist/capability-control/service.d.ts +70 -0
- package/dist/capability-control/service.js +147 -0
- package/dist/capability-control/types.d.ts +62 -0
- package/dist/capability-control/types.js +17 -0
- package/dist/cli/agent-server-stdio.d.ts +12 -1
- package/dist/cli/agent-server-stdio.js +118 -18
- package/dist/cli/agent-server-tcp.d.ts +20 -0
- package/dist/cli/agent-server-tcp.js +120 -0
- package/dist/cli/graceful-shutdown.d.ts +27 -0
- package/dist/cli/graceful-shutdown.js +30 -0
- package/dist/context/compaction.js +23 -15
- package/dist/context/manager.d.ts +0 -20
- package/dist/context/manager.js +0 -37
- package/dist/cron/cron-runtime.d.ts +2 -0
- package/dist/cron/cron-runtime.js +2 -0
- package/dist/cron/cron-store.d.ts +2 -0
- package/dist/cron/cron-store.js +2 -0
- package/dist/cron/scheduler.d.ts +5 -32
- package/dist/cron/scheduler.js +5 -113
- package/dist/data/openrouter-sync.js +1 -1
- package/dist/engine/engine.d.ts +256 -16
- package/dist/engine/engine.js +856 -200
- package/dist/engine/goal.d.ts +42 -0
- package/dist/engine/goal.js +52 -0
- package/dist/engine/image-compression.d.ts +81 -0
- package/dist/engine/image-compression.js +186 -0
- package/dist/engine/image-policy.d.ts +152 -0
- package/dist/engine/image-policy.js +165 -0
- package/dist/engine/reactive-threshold.d.ts +13 -0
- package/dist/engine/reactive-threshold.js +17 -0
- package/dist/engine/runtime.js +7 -0
- package/dist/engine/session-title.d.ts +13 -0
- package/dist/engine/session-title.js +34 -0
- package/dist/engine/turn-loop.d.ts +29 -8
- package/dist/engine/turn-loop.js +498 -316
- package/dist/git/parse-log.d.ts +13 -0
- package/dist/git/parse-log.js +21 -0
- package/dist/git/utils.d.ts +8 -6
- package/dist/git/utils.js +57 -26
- package/dist/git/worktree.js +33 -18
- package/dist/hooks/events.d.ts +23 -1
- package/dist/hooks/goal-stop-hook.d.ts +30 -0
- package/dist/hooks/goal-stop-hook.js +102 -0
- package/dist/hooks/hook-output.d.ts +33 -0
- package/dist/hooks/hook-output.js +79 -0
- package/dist/hooks/registry.d.ts +7 -0
- package/dist/hooks/registry.js +38 -1
- package/dist/hooks/shell-runner.d.ts +2 -0
- package/dist/hooks/shell-runner.js +43 -2
- package/dist/index.d.ts +32 -7
- package/dist/index.js +30 -3
- package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
- package/dist/llm/capabilities/reasoning-control.js +38 -0
- package/dist/llm/capabilities/rules.js +33 -3
- package/dist/llm/capabilities/types.d.ts +22 -2
- package/dist/llm/clamp-max-tokens.d.ts +11 -0
- package/dist/llm/clamp-max-tokens.js +17 -0
- package/dist/llm/client-base.d.ts +41 -4
- package/dist/llm/client-base.js +67 -13
- package/dist/llm/client-factory.d.ts +3 -3
- package/dist/llm/client-factory.js +2 -2
- package/dist/llm/model-pool.d.ts +15 -11
- package/dist/llm/model-pool.js +23 -20
- package/dist/llm/provider-catalog.d.ts +2 -2
- package/dist/llm/providers/anthropic.d.ts +32 -2
- package/dist/llm/providers/anthropic.js +147 -8
- package/dist/llm/providers/openai.d.ts +11 -2
- package/dist/llm/providers/openai.js +253 -52
- package/dist/llm/reasoning-setting.d.ts +51 -0
- package/dist/llm/reasoning-setting.js +32 -0
- package/dist/llm/stop-reason.d.ts +13 -0
- package/dist/llm/stop-reason.js +21 -0
- package/dist/llm/strip-vision.d.ts +32 -0
- package/dist/llm/strip-vision.js +94 -0
- package/dist/llm/types.d.ts +5 -6
- package/dist/logging/logger.js +18 -4
- package/dist/logging/sanitize-messages.d.ts +10 -0
- package/dist/logging/sanitize-messages.js +90 -0
- package/dist/lsp/client.js +28 -8
- package/dist/lsp/manager.js +2 -1
- package/dist/lsp/root-path.d.ts +9 -0
- package/dist/lsp/root-path.js +12 -0
- package/dist/onboarding.js +18 -11
- package/dist/plugins/gitOps.d.ts +34 -0
- package/dist/plugins/gitOps.js +59 -3
- package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
- package/dist/plugins/installer/codex/convertAgents.js +55 -0
- package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
- package/dist/plugins/installer/codex/convertMcp.js +45 -0
- package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
- package/dist/plugins/installer/codex/convertSkills.js +33 -0
- package/dist/plugins/installer/detectFormat.d.ts +2 -0
- package/dist/plugins/installer/detectFormat.js +6 -0
- package/dist/plugins/installer/install.d.ts +7 -0
- package/dist/plugins/installer/install.js +99 -0
- package/dist/plugins/installer/installFromSource.d.ts +13 -0
- package/dist/plugins/installer/installFromSource.js +45 -0
- package/dist/plugins/installer/list.d.ts +9 -0
- package/dist/plugins/installer/list.js +30 -0
- package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
- package/dist/plugins/installer/loadPluginAgents.js +23 -0
- package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
- package/dist/plugins/installer/loadPluginMcp.js +74 -0
- package/dist/plugins/installer/parseSource.d.ts +20 -0
- package/dist/plugins/installer/parseSource.js +74 -0
- package/dist/plugins/installer/paths.d.ts +5 -0
- package/dist/plugins/installer/paths.js +25 -0
- package/dist/plugins/installer/types.d.ts +49 -0
- package/dist/plugins/installer/types.js +27 -0
- package/dist/plugins/installer/uninstall.d.ts +2 -0
- package/dist/plugins/installer/uninstall.js +14 -0
- package/dist/plugins/installer/update.d.ts +14 -0
- package/dist/plugins/installer/update.js +65 -0
- package/dist/plugins/loadPluginHooks.d.ts +8 -1
- package/dist/plugins/loadPluginHooks.js +11 -1
- package/dist/plugins/marketplaceManager.d.ts +8 -1
- package/dist/plugins/marketplaceManager.js +38 -4
- package/dist/plugins/pluginCommandHook.js +34 -0
- package/dist/plugins/pluginInstaller.d.ts +21 -0
- package/dist/plugins/pluginInstaller.js +128 -29
- package/dist/plugins/schemas.js +40 -6
- package/dist/plugins/types.d.ts +4 -0
- package/dist/preset/index.js +16 -0
- package/dist/prompt/composer.d.ts +10 -0
- package/dist/prompt/composer.js +29 -4
- package/dist/prompt/sections/orchestration.md +8 -0
- package/dist/protocol/chat-session-manager.d.ts +8 -1
- package/dist/protocol/chat-session-manager.js +10 -0
- package/dist/protocol/chat-session.d.ts +17 -0
- package/dist/protocol/chat-session.js +28 -0
- package/dist/protocol/redact.d.ts +50 -0
- package/dist/protocol/redact.js +71 -0
- package/dist/protocol/server.d.ts +41 -2
- package/dist/protocol/server.js +231 -33
- package/dist/protocol/tcp-transport.d.ts +45 -0
- package/dist/protocol/tcp-transport.js +74 -0
- package/dist/protocol/types.d.ts +23 -0
- package/dist/protocol/types.js +6 -0
- package/dist/remote/bridge.d.ts +11 -2
- package/dist/remote/bridge.js +61 -33
- package/dist/run/ArtifactTracker.js +5 -4
- package/dist/run/EngineRunner.d.ts +27 -1
- package/dist/run/EngineRunner.js +46 -8
- package/dist/run/FileRunStore.js +32 -8
- package/dist/run/RunLock.d.ts +28 -2
- package/dist/run/RunLock.js +49 -7
- package/dist/run/RunManager.js +51 -3
- package/dist/run/RunQueue.d.ts +1 -0
- package/dist/run/RunQueue.js +8 -2
- package/dist/run/factory.d.ts +7 -0
- package/dist/run/factory.js +1 -0
- package/dist/run/index.d.ts +2 -2
- package/dist/run/index.js +1 -1
- package/dist/run/redirect-target.d.ts +7 -0
- package/dist/run/redirect-target.js +13 -0
- package/dist/run/types.js +1 -1
- package/dist/services/browser-open.d.ts +13 -0
- package/dist/services/browser-open.js +16 -0
- package/dist/services/dream-consolidation.d.ts +57 -0
- package/dist/services/dream-consolidation.js +151 -0
- package/dist/services/memory-orchestrator.js +18 -0
- package/dist/services/notifier.d.ts +8 -0
- package/dist/services/notifier.js +39 -25
- package/dist/services/oauth.js +5 -7
- package/dist/services/session-memory-sort.d.ts +8 -0
- package/dist/services/session-memory-sort.js +9 -0
- package/dist/services/session-memory.js +8 -9
- package/dist/session/file-history.js +10 -3
- package/dist/session/session-manager.d.ts +23 -1
- package/dist/session/session-manager.js +73 -2
- package/dist/settings/disk-defaults.d.ts +35 -0
- package/dist/settings/disk-defaults.js +24 -0
- package/dist/settings/manager.d.ts +45 -1
- package/dist/settings/manager.js +121 -17
- package/dist/settings/manager.test.d.ts +1 -0
- package/dist/settings/manager.test.js +73 -0
- package/dist/settings/personalization.d.ts +19 -0
- package/dist/settings/personalization.js +7 -0
- package/dist/settings/schema.d.ts +1163 -126
- package/dist/settings/schema.js +126 -10
- package/dist/skills/scanner.d.ts +13 -6
- package/dist/skills/scanner.js +23 -4
- package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
- package/dist/tool-system/builtin/add-marketplace.js +76 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +15 -0
- package/dist/tool-system/builtin/agent-registry.js +14 -4
- package/dist/tool-system/builtin/agent.d.ts +55 -0
- package/dist/tool-system/builtin/agent.js +165 -17
- package/dist/tool-system/builtin/apply-patch/index.js +22 -3
- package/dist/tool-system/builtin/arena.js +6 -7
- package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
- package/dist/tool-system/builtin/complete-goal.js +45 -0
- package/dist/tool-system/builtin/cron.js +53 -9
- package/dist/tool-system/builtin/edit.d.ts +2 -1
- package/dist/tool-system/builtin/edit.js +6 -1
- package/dist/tool-system/builtin/generate-image.d.ts +25 -0
- package/dist/tool-system/builtin/generate-image.js +146 -0
- package/dist/tool-system/builtin/glob.js +7 -0
- package/dist/tool-system/builtin/grep.js +6 -0
- package/dist/tool-system/builtin/index.d.ts +18 -1
- package/dist/tool-system/builtin/index.js +62 -1
- package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
- package/dist/tool-system/builtin/notebook-edit.js +8 -1
- package/dist/tool-system/builtin/read.d.ts +2 -1
- package/dist/tool-system/builtin/read.js +9 -1
- package/dist/tool-system/builtin/skill.js +23 -5
- package/dist/tool-system/builtin/sleep.js +9 -3
- package/dist/tool-system/builtin/task.js +4 -1
- package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
- package/dist/tool-system/builtin/update-automation-memory.js +60 -0
- package/dist/tool-system/builtin/view-image.d.ts +18 -0
- package/dist/tool-system/builtin/view-image.js +96 -0
- package/dist/tool-system/builtin/web-search.d.ts +4 -1
- package/dist/tool-system/builtin/web-search.js +36 -3
- package/dist/tool-system/builtin/write.d.ts +2 -1
- package/dist/tool-system/builtin/write.js +7 -1
- package/dist/tool-system/context.d.ts +43 -0
- package/dist/tool-system/executor.d.ts +0 -1
- package/dist/tool-system/executor.js +34 -40
- package/dist/tool-system/investigation-guard.d.ts +4 -1
- package/dist/tool-system/investigation-guard.js +19 -3
- package/dist/tool-system/mcp-manager.d.ts +36 -12
- package/dist/tool-system/mcp-manager.js +173 -10
- package/dist/tool-system/path-policy.d.ts +94 -0
- package/dist/tool-system/path-policy.js +279 -0
- package/dist/tool-system/permission.js +43 -50
- package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
- package/dist/tool-system/plan-mode-allowlist.js +43 -0
- package/dist/tool-system/registry.js +12 -1
- package/dist/tool-system/validation.d.ts +10 -0
- package/dist/tool-system/validation.js +16 -3
- package/dist/types.d.ts +90 -15
- package/dist/utils/format.js +4 -2
- package/dist/utils/lockfile.js +9 -2
- package/dist/utils/theme.d.ts +6 -0
- package/dist/utils/theme.js +4 -2
- package/package.json +2 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Abstract base class for all LLM provider clients.
|
|
3
3
|
*/
|
|
4
|
-
import type { LLMConfig, LLMResponse, TokenUsage } from "../types.js";
|
|
4
|
+
import type { ClientDefaults, LLMConfig, LLMResponse, TokenUsage } from "../types.js";
|
|
5
5
|
import type { CreateMessageOptions, LLMUsageTracker } from "./types.js";
|
|
6
6
|
export declare abstract class LLMClientBase {
|
|
7
7
|
protected readonly config: LLMConfig;
|
|
8
8
|
readonly provider: string;
|
|
9
9
|
readonly model: string;
|
|
10
|
+
readonly maxTokens: number | undefined;
|
|
10
11
|
readonly temperature: number;
|
|
11
|
-
readonly maxTokens: number;
|
|
12
12
|
readonly timeout: number;
|
|
13
13
|
readonly retryMaxAttempts: number;
|
|
14
|
-
readonly
|
|
14
|
+
readonly imageDetail?: ClientDefaults["imageDetail"];
|
|
15
15
|
/**
|
|
16
16
|
* Process-wide hook fired on every LLM response. The CLI installs this in
|
|
17
17
|
* main.ts to feed the cost tracker; lives on the base class so every code
|
|
@@ -20,10 +20,47 @@ export declare abstract class LLMClientBase {
|
|
|
20
20
|
*/
|
|
21
21
|
static onUsage?: (model: string, usage: TokenUsage) => void;
|
|
22
22
|
protected usage: LLMUsageTracker;
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* `config` carries model identity (provider/model/apiKey/baseUrl/maxTokens/
|
|
25
|
+
* thinking/providerKind). `defaults` carries cross-model runtime knobs
|
|
26
|
+
* (temperature/timeout/retryMaxAttempts/imageDetail) — those are owned by
|
|
27
|
+
* the Engine and stay stable across hot model switches.
|
|
28
|
+
*/
|
|
29
|
+
constructor(config: LLMConfig, defaults?: ClientDefaults);
|
|
24
30
|
protected abstract initClient(): void;
|
|
25
31
|
abstract createMessage(options: CreateMessageOptions): Promise<LLMResponse>;
|
|
26
32
|
protected recordUsage(usage: TokenUsage, options?: CreateMessageOptions): void;
|
|
27
33
|
getUsage(): LLMUsageTracker;
|
|
28
34
|
protected withRetry<T>(fn: () => Promise<T>, maxAttempts?: number): Promise<T>;
|
|
29
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Detect HTTP 4xx errors from provider SDKs so withRetry can bail
|
|
38
|
+
* without burning backoff time. OpenAI/Anthropic SDKs attach a numeric
|
|
39
|
+
* `status` to their error objects; we treat 400-499 as non-retryable
|
|
40
|
+
* (429 is handled separately above as a rate-limit).
|
|
41
|
+
*
|
|
42
|
+
* The provider clients also wrap SDK errors into `new LLMError(msg,
|
|
43
|
+
* provider, { status })`, where the status lands in
|
|
44
|
+
* `FrameworkError.details.status` rather than a top-level `.status`.
|
|
45
|
+
* We read both so a wrapped 400/401/404 isn't retried 3× (~9 s wasted)
|
|
46
|
+
* before finally surfacing.
|
|
47
|
+
*
|
|
48
|
+
* Network errors and 5xx fall through and remain retryable.
|
|
49
|
+
*
|
|
50
|
+
* Exported for unit testing.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isClientError(err: unknown): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Detect a user/run cancellation. The OpenAI and Anthropic SDKs throw
|
|
55
|
+
* `APIUserAbortError` when a request's AbortSignal fires mid-flight; the
|
|
56
|
+
* providers rethrow it unchanged (see handleApiError). It carries no HTTP
|
|
57
|
+
* `status`, so `isClientError` can't recognise it — without an explicit
|
|
58
|
+
* check it falls through to `withRetry`'s generic branch and gets retried.
|
|
59
|
+
*
|
|
60
|
+
* We match by error name rather than `instanceof` to avoid importing the
|
|
61
|
+
* provider SDKs into the base class: `APIUserAbortError` from the SDKs, and
|
|
62
|
+
* the WHATWG `AbortError` from `fetch`/AbortController, both surface here.
|
|
63
|
+
*
|
|
64
|
+
* Exported for unit testing.
|
|
65
|
+
*/
|
|
66
|
+
export declare function isAbortError(err: unknown): boolean;
|
package/dist/llm/client-base.js
CHANGED
|
@@ -7,11 +7,11 @@ export class LLMClientBase {
|
|
|
7
7
|
config;
|
|
8
8
|
provider;
|
|
9
9
|
model;
|
|
10
|
-
temperature;
|
|
11
10
|
maxTokens;
|
|
11
|
+
temperature;
|
|
12
12
|
timeout;
|
|
13
13
|
retryMaxAttempts;
|
|
14
|
-
|
|
14
|
+
imageDetail;
|
|
15
15
|
/**
|
|
16
16
|
* Process-wide hook fired on every LLM response. The CLI installs this in
|
|
17
17
|
* main.ts to feed the cost tracker; lives on the base class so every code
|
|
@@ -26,15 +26,26 @@ export class LLMClientBase {
|
|
|
26
26
|
totalTokens: 0,
|
|
27
27
|
requestCount: 0,
|
|
28
28
|
};
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* `config` carries model identity (provider/model/apiKey/baseUrl/maxTokens/
|
|
31
|
+
* thinking/providerKind). `defaults` carries cross-model runtime knobs
|
|
32
|
+
* (temperature/timeout/retryMaxAttempts/imageDetail) — those are owned by
|
|
33
|
+
* the Engine and stay stable across hot model switches.
|
|
34
|
+
*/
|
|
35
|
+
constructor(config, defaults) {
|
|
30
36
|
this.config = config;
|
|
31
37
|
this.provider = config.provider;
|
|
32
38
|
this.model = config.model;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
// No `?? 8192` fallback: when the model's output ceiling is unknown we keep
|
|
40
|
+
// `undefined` so each provider decides (OpenAI omits the field and lets the
|
|
41
|
+
// endpoint use its own max; Anthropic, where max_tokens is required, supplies
|
|
42
|
+
// a conservative default at request time). Forcing 8192 here truncated long
|
|
43
|
+
// outputs — a streamed tool-arg JSON cut off mid-token → "Missing file_path".
|
|
44
|
+
this.maxTokens = config.maxTokens;
|
|
45
|
+
this.temperature = defaults?.temperature ?? 0.3;
|
|
46
|
+
this.timeout = defaults?.timeout ?? 120_000;
|
|
47
|
+
this.retryMaxAttempts = defaults?.retryMaxAttempts ?? 3;
|
|
48
|
+
this.imageDetail = defaults?.imageDetail;
|
|
38
49
|
this.initClient();
|
|
39
50
|
}
|
|
40
51
|
recordUsage(usage, options) {
|
|
@@ -61,6 +72,20 @@ export class LLMClientBase {
|
|
|
61
72
|
lastError = err;
|
|
62
73
|
if (err instanceof ContextLimitError)
|
|
63
74
|
throw err;
|
|
75
|
+
// User pressed ESC / Stop, or the run's AbortSignal fired. The SDK
|
|
76
|
+
// throws APIUserAbortError (no HTTP status, so isClientError below
|
|
77
|
+
// can't catch it). Retrying re-issues the same aborted request 3×
|
|
78
|
+
// with growing backoff — ~40 s of dead time ending in llm.exhausted,
|
|
79
|
+
// for work the user explicitly cancelled. Surface immediately.
|
|
80
|
+
if (isAbortError(err)) {
|
|
81
|
+
logger.warn("llm.abort_no_retry", {
|
|
82
|
+
cat: "llm",
|
|
83
|
+
provider: this.provider,
|
|
84
|
+
model: this.model,
|
|
85
|
+
error: err.message,
|
|
86
|
+
});
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
64
89
|
if (err instanceof LLMRateLimitError) {
|
|
65
90
|
const waitMs = (err.retryAfter ?? attempt * 2) * 1000;
|
|
66
91
|
logger.warn("llm.retry", {
|
|
@@ -119,19 +144,48 @@ export class LLMClientBase {
|
|
|
119
144
|
}
|
|
120
145
|
/**
|
|
121
146
|
* Detect HTTP 4xx errors from provider SDKs so withRetry can bail
|
|
122
|
-
* without burning backoff time. OpenAI/Anthropic SDKs
|
|
123
|
-
*
|
|
124
|
-
*
|
|
147
|
+
* without burning backoff time. OpenAI/Anthropic SDKs attach a numeric
|
|
148
|
+
* `status` to their error objects; we treat 400-499 as non-retryable
|
|
149
|
+
* (429 is handled separately above as a rate-limit).
|
|
150
|
+
*
|
|
151
|
+
* The provider clients also wrap SDK errors into `new LLMError(msg,
|
|
152
|
+
* provider, { status })`, where the status lands in
|
|
153
|
+
* `FrameworkError.details.status` rather than a top-level `.status`.
|
|
154
|
+
* We read both so a wrapped 400/401/404 isn't retried 3× (~9 s wasted)
|
|
155
|
+
* before finally surfacing.
|
|
125
156
|
*
|
|
126
157
|
* Network errors and 5xx fall through and remain retryable.
|
|
158
|
+
*
|
|
159
|
+
* Exported for unit testing.
|
|
127
160
|
*/
|
|
128
|
-
function isClientError(err) {
|
|
161
|
+
export function isClientError(err) {
|
|
129
162
|
if (typeof err !== "object" || err === null)
|
|
130
163
|
return false;
|
|
131
|
-
const
|
|
164
|
+
const top = err.status;
|
|
165
|
+
const buried = err.details?.status;
|
|
166
|
+
const status = typeof top === "number" ? top : typeof buried === "number" ? buried : undefined;
|
|
132
167
|
if (typeof status !== "number")
|
|
133
168
|
return false;
|
|
134
169
|
if (status === 429)
|
|
135
170
|
return false;
|
|
136
171
|
return status >= 400 && status < 500;
|
|
137
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Detect a user/run cancellation. The OpenAI and Anthropic SDKs throw
|
|
175
|
+
* `APIUserAbortError` when a request's AbortSignal fires mid-flight; the
|
|
176
|
+
* providers rethrow it unchanged (see handleApiError). It carries no HTTP
|
|
177
|
+
* `status`, so `isClientError` can't recognise it — without an explicit
|
|
178
|
+
* check it falls through to `withRetry`'s generic branch and gets retried.
|
|
179
|
+
*
|
|
180
|
+
* We match by error name rather than `instanceof` to avoid importing the
|
|
181
|
+
* provider SDKs into the base class: `APIUserAbortError` from the SDKs, and
|
|
182
|
+
* the WHATWG `AbortError` from `fetch`/AbortController, both surface here.
|
|
183
|
+
*
|
|
184
|
+
* Exported for unit testing.
|
|
185
|
+
*/
|
|
186
|
+
export function isAbortError(err) {
|
|
187
|
+
if (typeof err !== "object" || err === null)
|
|
188
|
+
return false;
|
|
189
|
+
const name = err.name;
|
|
190
|
+
return name === "APIUserAbortError" || name === "AbortError";
|
|
191
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LLM provider factory with registry pattern.
|
|
3
3
|
*/
|
|
4
|
-
import type { LLMConfig } from "../types.js";
|
|
4
|
+
import type { ClientDefaults, LLMConfig } from "../types.js";
|
|
5
5
|
import type { LLMClientBase } from "./client-base.js";
|
|
6
|
-
type ProviderConstructor = new (config: LLMConfig) => LLMClientBase;
|
|
6
|
+
type ProviderConstructor = new (config: LLMConfig, defaults?: ClientDefaults) => LLMClientBase;
|
|
7
7
|
declare const PROVIDER_REGISTRY: Map<string, ProviderConstructor>;
|
|
8
8
|
export declare function registerProvider(name: string, cls: ProviderConstructor): void;
|
|
9
|
-
export declare function createLLMClient(config: LLMConfig): Promise<LLMClientBase>;
|
|
9
|
+
export declare function createLLMClient(config: LLMConfig, defaults?: ClientDefaults): Promise<LLMClientBase>;
|
|
10
10
|
export { PROVIDER_REGISTRY };
|
|
@@ -6,7 +6,7 @@ const PROVIDER_REGISTRY = new Map();
|
|
|
6
6
|
export function registerProvider(name, cls) {
|
|
7
7
|
PROVIDER_REGISTRY.set(name, cls);
|
|
8
8
|
}
|
|
9
|
-
export async function createLLMClient(config) {
|
|
9
|
+
export async function createLLMClient(config, defaults) {
|
|
10
10
|
let Cls = PROVIDER_REGISTRY.get(config.provider);
|
|
11
11
|
if (!Cls) {
|
|
12
12
|
// Auto-register built-in providers on first use
|
|
@@ -24,6 +24,6 @@ export async function createLLMClient(config) {
|
|
|
24
24
|
if (!Cls) {
|
|
25
25
|
throw new LLMError(`Unknown LLM provider: ${config.provider}. Available: ${[...PROVIDER_REGISTRY.keys()].join(", ")}`);
|
|
26
26
|
}
|
|
27
|
-
return new Cls(config);
|
|
27
|
+
return new Cls(config, defaults);
|
|
28
28
|
}
|
|
29
29
|
export { PROVIDER_REGISTRY };
|
package/dist/llm/model-pool.d.ts
CHANGED
|
@@ -30,12 +30,11 @@ export interface ModelEntry {
|
|
|
30
30
|
* come from the catalog unless the entry overrides them. */
|
|
31
31
|
providerKey?: string;
|
|
32
32
|
/**
|
|
33
|
-
* Per-model
|
|
34
|
-
* (ProviderCatalog entry's `
|
|
35
|
-
* provider need different defaults — e.g.
|
|
36
|
-
* V4 Flash on.
|
|
33
|
+
* Per-model reasoning override. Wins over the provider-level setting
|
|
34
|
+
* (ProviderCatalog entry's `reasoning`). Useful when models in the same
|
|
35
|
+
* provider need different defaults — e.g. one model off but another on.
|
|
37
36
|
*/
|
|
38
|
-
|
|
37
|
+
reasoning?: import("./reasoning-setting.js").ReasoningSetting;
|
|
39
38
|
}
|
|
40
39
|
export declare class ModelPool {
|
|
41
40
|
private models;
|
|
@@ -83,13 +82,18 @@ export declare class ModelPool {
|
|
|
83
82
|
/** Check if a key exists. */
|
|
84
83
|
has(key: string): boolean;
|
|
85
84
|
/**
|
|
86
|
-
* Build an LLMConfig
|
|
87
|
-
*
|
|
85
|
+
* Build an LLMConfig (pure model identity) for the given entry. Cross-model
|
|
86
|
+
* runtime knobs (temperature/timeout/retryMaxAttempts/imageDetail) are NOT
|
|
87
|
+
* part of LLMConfig — they live on the Engine as ClientDefaults and are
|
|
88
|
+
* threaded into the LLMClient independently. That separation lets hot-
|
|
89
|
+
* switching the model replace this object wholesale without touching the
|
|
90
|
+
* user's runtime preferences (and without leaking the old model's settings
|
|
91
|
+
* onto a model that doesn't share them — e.g. 384k-output deepseek bleeding
|
|
92
|
+
* into 128k-cap gpt-5.5).
|
|
88
93
|
*/
|
|
89
|
-
toLLMConfig(entry: ModelEntry
|
|
94
|
+
toLLMConfig(entry: ModelEntry): LLMConfig;
|
|
90
95
|
/**
|
|
91
|
-
* Build an LLMConfig for the active model (or a specific key)
|
|
92
|
-
* merging with a base config.
|
|
96
|
+
* Build an LLMConfig for the active model (or a specific key).
|
|
93
97
|
*/
|
|
94
|
-
resolveLLMConfig(key?: string
|
|
98
|
+
resolveLLMConfig(key?: string): LLMConfig | undefined;
|
|
95
99
|
}
|
package/dist/llm/model-pool.js
CHANGED
|
@@ -193,10 +193,16 @@ export class ModelPool {
|
|
|
193
193
|
return this.models.has(key);
|
|
194
194
|
}
|
|
195
195
|
/**
|
|
196
|
-
* Build an LLMConfig
|
|
197
|
-
*
|
|
196
|
+
* Build an LLMConfig (pure model identity) for the given entry. Cross-model
|
|
197
|
+
* runtime knobs (temperature/timeout/retryMaxAttempts/imageDetail) are NOT
|
|
198
|
+
* part of LLMConfig — they live on the Engine as ClientDefaults and are
|
|
199
|
+
* threaded into the LLMClient independently. That separation lets hot-
|
|
200
|
+
* switching the model replace this object wholesale without touching the
|
|
201
|
+
* user's runtime preferences (and without leaking the old model's settings
|
|
202
|
+
* onto a model that doesn't share them — e.g. 384k-output deepseek bleeding
|
|
203
|
+
* into 128k-cap gpt-5.5).
|
|
198
204
|
*/
|
|
199
|
-
toLLMConfig(entry
|
|
205
|
+
toLLMConfig(entry) {
|
|
200
206
|
const fromCat = entry.providerKey && this.providerCatalog
|
|
201
207
|
? this.providerCatalog.get(entry.providerKey)
|
|
202
208
|
: undefined;
|
|
@@ -215,32 +221,29 @@ export class ModelPool {
|
|
|
215
221
|
kindToClientProvider(fromCat?.kind) ||
|
|
216
222
|
"openai",
|
|
217
223
|
model: entry.model,
|
|
218
|
-
apiKey: entry.apiKey ?? fromCat?.apiKey
|
|
219
|
-
baseUrl: entry.baseUrl ?? fromCat?.baseUrl
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
...(entry.
|
|
227
|
-
? {
|
|
224
|
+
apiKey: entry.apiKey ?? fromCat?.apiKey,
|
|
225
|
+
baseUrl: entry.baseUrl ?? fromCat?.baseUrl,
|
|
226
|
+
// No invented default: undefined lets each client apply its own fallback
|
|
227
|
+
// (OpenAI omits the token field entirely; Anthropic uses its own constant)
|
|
228
|
+
// instead of fabricating 8192, which silently truncates long outputs and
|
|
229
|
+
// masks the real per-model cap.
|
|
230
|
+
maxTokens: entry.maxOutputTokens,
|
|
231
|
+
// reasoning: entry overrides catalog. No base fallback — see class doc.
|
|
232
|
+
...(entry.reasoning ?? fromCat?.reasoning
|
|
233
|
+
? { reasoning: entry.reasoning ?? fromCat?.reasoning }
|
|
228
234
|
: {}),
|
|
229
235
|
// Carry the catalog kind through so the capability layer can pick
|
|
230
|
-
// per-(kind, model) request-shape rules.
|
|
231
|
-
// unusual case where an entry has a provider but no providerKey
|
|
232
|
-
// (legacy / arena-injected models).
|
|
236
|
+
// per-(kind, model) request-shape rules.
|
|
233
237
|
...(fromCat?.kind ? { providerKind: fromCat.kind } : {}),
|
|
234
238
|
};
|
|
235
239
|
}
|
|
236
240
|
/**
|
|
237
|
-
* Build an LLMConfig for the active model (or a specific key)
|
|
238
|
-
* merging with a base config.
|
|
241
|
+
* Build an LLMConfig for the active model (or a specific key).
|
|
239
242
|
*/
|
|
240
|
-
resolveLLMConfig(key
|
|
243
|
+
resolveLLMConfig(key) {
|
|
241
244
|
const entry = this.get(key);
|
|
242
245
|
if (!entry)
|
|
243
246
|
return undefined;
|
|
244
|
-
return this.toLLMConfig(entry
|
|
247
|
+
return this.toLLMConfig(entry);
|
|
245
248
|
}
|
|
246
249
|
}
|
|
@@ -13,8 +13,8 @@ export interface ProviderConfig {
|
|
|
13
13
|
apiKey?: string;
|
|
14
14
|
protocol?: "openai-compat" | "anthropic-style";
|
|
15
15
|
modelsPath?: string;
|
|
16
|
-
/** Default
|
|
17
|
-
|
|
16
|
+
/** Default reasoning/thinking setting for this provider. See LLMConfig. */
|
|
17
|
+
reasoning?: import("./reasoning-setting.js").ReasoningSetting;
|
|
18
18
|
}
|
|
19
19
|
export declare class ProviderCatalog {
|
|
20
20
|
private byKey;
|
|
@@ -1,14 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anthropic Claude provider using @anthropic-ai/sdk.
|
|
3
3
|
*/
|
|
4
|
-
import type { LLMConfig, LLMResponse } from "../../types.js";
|
|
4
|
+
import type { ClientDefaults, LLMConfig, LLMResponse } from "../../types.js";
|
|
5
5
|
import type { CreateMessageOptions } from "../types.js";
|
|
6
6
|
import { LLMClientBase } from "../client-base.js";
|
|
7
7
|
export declare class AnthropicClient extends LLMClientBase {
|
|
8
8
|
private _client;
|
|
9
|
-
constructor(config: LLMConfig);
|
|
9
|
+
constructor(config: LLMConfig, defaults?: ClientDefaults);
|
|
10
10
|
protected initClient(): void;
|
|
11
11
|
private get client();
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the capability descriptor for the current model. `providerKind`
|
|
14
|
+
* defaults to "anthropic" (this client only serves the Anthropic API).
|
|
15
|
+
* Memoized — the model doesn't change mid-client.
|
|
16
|
+
*/
|
|
17
|
+
private _capability;
|
|
18
|
+
private get capability();
|
|
19
|
+
/**
|
|
20
|
+
* Translate the resolved ReasoningSetting into Anthropic's `thinking` field,
|
|
21
|
+
* honoring the model's reasoning shape (rules.ts):
|
|
22
|
+
*
|
|
23
|
+
* - `anthropic-budget` (Claude 4.0–4.5): supports explicit
|
|
24
|
+
* `thinking:{type:"enabled", budget_tokens≥minBudgetTokens}`.
|
|
25
|
+
* · mode "budget" → use budgetTokens (clamped up to minBudgetTokens)
|
|
26
|
+
* · mode "on" → default budget (clamped up to minBudgetTokens)
|
|
27
|
+
* · mode "effort" → this family is budget-typed, not effort-typed, so
|
|
28
|
+
* treat any effort selection as "on" with the default.
|
|
29
|
+
* · mode "off" / unset → return undefined (omit the field).
|
|
30
|
+
* - `anthropic-adaptive` (Claude 4.6+): thinking is automatic and NOT
|
|
31
|
+
* controllable; sending `type:"enabled"` 400s. Always omit → undefined.
|
|
32
|
+
* - anything else (Claude 3.x catch-all → kind "none"): omit → undefined.
|
|
33
|
+
*
|
|
34
|
+
* `maxTokens` is the request's max_tokens. Anthropic requires
|
|
35
|
+
* max_tokens > budget_tokens when thinking is enabled, so we cap the budget
|
|
36
|
+
* just below it (leaving headroom for the visible answer). The minBudgetTokens
|
|
37
|
+
* floor still wins — if even the floor doesn't fit under max_tokens the model
|
|
38
|
+
* itself rejects it, which surfaces as a clear API error rather than us
|
|
39
|
+
* silently sending a degenerate budget.
|
|
40
|
+
*/
|
|
41
|
+
private buildThinking;
|
|
12
42
|
createMessage(options: CreateMessageOptions): Promise<LLMResponse>;
|
|
13
43
|
private nonStreamMessage;
|
|
14
44
|
private streamMessage;
|
|
@@ -6,10 +6,24 @@ import { LLMClientBase } from "../client-base.js";
|
|
|
6
6
|
import { ContextLimitError, LLMError, LLMRateLimitError } from "../../exceptions.js";
|
|
7
7
|
import { logger } from "../../logging/logger.js";
|
|
8
8
|
import { countTokens } from "../token-counter.js";
|
|
9
|
+
import { capabilitiesFor } from "../capabilities/index.js";
|
|
10
|
+
import { stripVisionFromHistory } from "../strip-vision.js";
|
|
11
|
+
/**
|
|
12
|
+
* Anthropic's `max_tokens` is required, so unlike OpenAI we can't omit it when
|
|
13
|
+
* the model's ceiling is unknown. Use a conservative floor in that rare case
|
|
14
|
+
* (every catalog Anthropic model resolves a real value via resolveMaxOutput, so
|
|
15
|
+
* this only fires for an unconfigured/unknown model).
|
|
16
|
+
*/
|
|
17
|
+
const ANTHROPIC_FALLBACK_MAX_TOKENS = 4096;
|
|
18
|
+
/**
|
|
19
|
+
* Default thinking budget when a budget-capable model wants "thinking on" but
|
|
20
|
+
* no explicit token budget was given. Clamped up to the model's minimum.
|
|
21
|
+
*/
|
|
22
|
+
const ANTHROPIC_DEFAULT_THINKING_BUDGET = 4096;
|
|
9
23
|
export class AnthropicClient extends LLMClientBase {
|
|
10
24
|
_client = null;
|
|
11
|
-
constructor(config) {
|
|
12
|
-
super(config);
|
|
25
|
+
constructor(config, defaults) {
|
|
26
|
+
super(config, defaults);
|
|
13
27
|
}
|
|
14
28
|
initClient() {
|
|
15
29
|
// Lazy init
|
|
@@ -24,6 +38,68 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
24
38
|
}
|
|
25
39
|
return this._client;
|
|
26
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the capability descriptor for the current model. `providerKind`
|
|
43
|
+
* defaults to "anthropic" (this client only serves the Anthropic API).
|
|
44
|
+
* Memoized — the model doesn't change mid-client.
|
|
45
|
+
*/
|
|
46
|
+
_capability = null;
|
|
47
|
+
get capability() {
|
|
48
|
+
if (!this._capability) {
|
|
49
|
+
const kind = (this.config.providerKind ?? "anthropic");
|
|
50
|
+
this._capability = capabilitiesFor(kind, this.model);
|
|
51
|
+
}
|
|
52
|
+
return this._capability;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Translate the resolved ReasoningSetting into Anthropic's `thinking` field,
|
|
56
|
+
* honoring the model's reasoning shape (rules.ts):
|
|
57
|
+
*
|
|
58
|
+
* - `anthropic-budget` (Claude 4.0–4.5): supports explicit
|
|
59
|
+
* `thinking:{type:"enabled", budget_tokens≥minBudgetTokens}`.
|
|
60
|
+
* · mode "budget" → use budgetTokens (clamped up to minBudgetTokens)
|
|
61
|
+
* · mode "on" → default budget (clamped up to minBudgetTokens)
|
|
62
|
+
* · mode "effort" → this family is budget-typed, not effort-typed, so
|
|
63
|
+
* treat any effort selection as "on" with the default.
|
|
64
|
+
* · mode "off" / unset → return undefined (omit the field).
|
|
65
|
+
* - `anthropic-adaptive` (Claude 4.6+): thinking is automatic and NOT
|
|
66
|
+
* controllable; sending `type:"enabled"` 400s. Always omit → undefined.
|
|
67
|
+
* - anything else (Claude 3.x catch-all → kind "none"): omit → undefined.
|
|
68
|
+
*
|
|
69
|
+
* `maxTokens` is the request's max_tokens. Anthropic requires
|
|
70
|
+
* max_tokens > budget_tokens when thinking is enabled, so we cap the budget
|
|
71
|
+
* just below it (leaving headroom for the visible answer). The minBudgetTokens
|
|
72
|
+
* floor still wins — if even the floor doesn't fit under max_tokens the model
|
|
73
|
+
* itself rejects it, which surfaces as a clear API error rather than us
|
|
74
|
+
* silently sending a degenerate budget.
|
|
75
|
+
*/
|
|
76
|
+
buildThinking(reasoning, maxTokens) {
|
|
77
|
+
const cap = this.capability;
|
|
78
|
+
if (cap.reasoning.kind !== "anthropic-budget") {
|
|
79
|
+
// anthropic-adaptive and none: never send a thinking field.
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
if (!reasoning || reasoning.mode === "off") {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const min = cap.reasoning.minBudgetTokens;
|
|
86
|
+
// Anthropic constraint: max_tokens must STRICTLY exceed budget_tokens, and
|
|
87
|
+
// budget_tokens must be ≥ the model's minimum. If max_tokens can't fit a
|
|
88
|
+
// min-sized thinking block plus at least `min` tokens of answer, there is
|
|
89
|
+
// no valid budget — omit thinking entirely rather than emit budget_tokens
|
|
90
|
+
// ≥ max_tokens (which the API rejects with a 400). This is reachable for
|
|
91
|
+
// small-maxTokens auxiliary calls (judge/planner) on budget models.
|
|
92
|
+
const ceiling = maxTokens - min;
|
|
93
|
+
if (ceiling < min) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
let budget = reasoning.mode === "budget"
|
|
97
|
+
? reasoning.budgetTokens
|
|
98
|
+
: ANTHROPIC_DEFAULT_THINKING_BUDGET; // "on" or "effort" → default budget
|
|
99
|
+
// Clamp into [min, ceiling] — both bounds are now guaranteed ≥ min.
|
|
100
|
+
budget = Math.min(Math.max(budget, min), ceiling);
|
|
101
|
+
return { type: "enabled", budget_tokens: budget };
|
|
102
|
+
}
|
|
27
103
|
async createMessage(options) {
|
|
28
104
|
return this.withRetry(async () => {
|
|
29
105
|
const messages = this.buildMessages(options.messages);
|
|
@@ -61,9 +137,14 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
61
137
|
}
|
|
62
138
|
async nonStreamMessage(options, messages, tools) {
|
|
63
139
|
try {
|
|
140
|
+
// Per-call reasoning wins; otherwise fall back to the provider/model
|
|
141
|
+
// default (settings → LLMConfig.reasoning). Mirrors openai.ts.
|
|
142
|
+
const reasoning = options.reasoning ?? this.config.reasoning;
|
|
143
|
+
const maxTokens = options.maxTokens ?? this.maxTokens ?? ANTHROPIC_FALLBACK_MAX_TOKENS;
|
|
144
|
+
const thinking = this.buildThinking(reasoning, maxTokens);
|
|
64
145
|
const response = await this.client.messages.create({
|
|
65
146
|
model: this.model,
|
|
66
|
-
max_tokens:
|
|
147
|
+
max_tokens: maxTokens,
|
|
67
148
|
system: [
|
|
68
149
|
{
|
|
69
150
|
type: "text",
|
|
@@ -73,6 +154,7 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
73
154
|
],
|
|
74
155
|
messages,
|
|
75
156
|
...(tools?.length ? { tools } : {}),
|
|
157
|
+
...(thinking ? { thinking } : {}),
|
|
76
158
|
...(options.temperature !== undefined
|
|
77
159
|
? { temperature: options.temperature }
|
|
78
160
|
: { temperature: this.temperature }),
|
|
@@ -94,9 +176,12 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
94
176
|
}
|
|
95
177
|
async streamMessage(options, messages, tools) {
|
|
96
178
|
try {
|
|
179
|
+
const reasoning = options.reasoning ?? this.config.reasoning;
|
|
180
|
+
const maxTokens = options.maxTokens ?? this.maxTokens ?? ANTHROPIC_FALLBACK_MAX_TOKENS;
|
|
181
|
+
const thinking = this.buildThinking(reasoning, maxTokens);
|
|
97
182
|
const stream = this.client.messages.stream({
|
|
98
183
|
model: this.model,
|
|
99
|
-
max_tokens:
|
|
184
|
+
max_tokens: maxTokens,
|
|
100
185
|
system: [
|
|
101
186
|
{
|
|
102
187
|
type: "text",
|
|
@@ -106,6 +191,7 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
106
191
|
],
|
|
107
192
|
messages,
|
|
108
193
|
...(tools?.length ? { tools } : {}),
|
|
194
|
+
...(thinking ? { thinking } : {}),
|
|
109
195
|
...(options.temperature !== undefined
|
|
110
196
|
? { temperature: options.temperature }
|
|
111
197
|
: { temperature: this.temperature }),
|
|
@@ -114,6 +200,25 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
114
200
|
let currentToolName = "";
|
|
115
201
|
let currentToolId = "";
|
|
116
202
|
let currentToolInput = "";
|
|
203
|
+
// Abort-guarded emit: once the turn is cancelled, stop forwarding chunks
|
|
204
|
+
// to the UI. The SDK's event emitter can keep firing buffered text/
|
|
205
|
+
// contentBlock/inputJson events after abort() until its HTTP stream tears
|
|
206
|
+
// down; without this guard those leak to the UI after the user hit Stop
|
|
207
|
+
// ("content comes back after interrupt"). We also eagerly abort the SDK
|
|
208
|
+
// stream below so teardown starts immediately rather than waiting on the
|
|
209
|
+
// passed-in request signal alone.
|
|
210
|
+
const emit = (chunk) => {
|
|
211
|
+
if (options.signal?.aborted)
|
|
212
|
+
return;
|
|
213
|
+
options.onChunk?.(chunk);
|
|
214
|
+
};
|
|
215
|
+
if (options.signal) {
|
|
216
|
+
const sig = options.signal;
|
|
217
|
+
if (sig.aborted)
|
|
218
|
+
stream.abort();
|
|
219
|
+
else
|
|
220
|
+
sig.addEventListener("abort", () => stream.abort(), { once: true });
|
|
221
|
+
}
|
|
117
222
|
// Time-to-first-byte: log exactly once per stream so streaming-latency
|
|
118
223
|
// questions ("model felt slow tonight") get a clean number per request.
|
|
119
224
|
const streamStartedAt = Date.now();
|
|
@@ -129,14 +234,14 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
129
234
|
});
|
|
130
235
|
}
|
|
131
236
|
currentText += text;
|
|
132
|
-
|
|
237
|
+
emit({ type: "text", text, tokens: countTokens(text) });
|
|
133
238
|
});
|
|
134
239
|
stream.on("contentBlock", (block) => {
|
|
135
240
|
if (block.type === "tool_use") {
|
|
136
241
|
currentToolName = block.name;
|
|
137
242
|
currentToolId = block.id;
|
|
138
243
|
currentToolInput = "";
|
|
139
|
-
|
|
244
|
+
emit({
|
|
140
245
|
type: "tool_use_start",
|
|
141
246
|
toolCall: { id: block.id, toolName: block.name, args: {} },
|
|
142
247
|
});
|
|
@@ -145,7 +250,7 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
145
250
|
stream.on("inputJson", (_delta, snapshot) => {
|
|
146
251
|
currentToolInput = JSON.stringify(snapshot);
|
|
147
252
|
if (currentToolId) {
|
|
148
|
-
|
|
253
|
+
emit({
|
|
149
254
|
type: "tool_use_delta",
|
|
150
255
|
toolCall: {
|
|
151
256
|
id: currentToolId,
|
|
@@ -195,6 +300,7 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
195
300
|
};
|
|
196
301
|
}
|
|
197
302
|
buildMessages(messages) {
|
|
303
|
+
messages = stripVisionFromHistory(messages, this.capability.supportsVision);
|
|
198
304
|
const result = [];
|
|
199
305
|
for (const msg of messages) {
|
|
200
306
|
if (msg.role === "system")
|
|
@@ -218,10 +324,37 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
218
324
|
});
|
|
219
325
|
}
|
|
220
326
|
else if (block.type === "tool_result" && block.tool_use_id) {
|
|
327
|
+
let content;
|
|
328
|
+
if (typeof block.content === "string") {
|
|
329
|
+
content = block.content;
|
|
330
|
+
}
|
|
331
|
+
else if (Array.isArray(block.content)) {
|
|
332
|
+
const parts = [];
|
|
333
|
+
for (const part of block.content) {
|
|
334
|
+
if (part.type === "text" && part.text) {
|
|
335
|
+
parts.push({ type: "text", text: part.text });
|
|
336
|
+
}
|
|
337
|
+
else if (part.type === "image" && part.source) {
|
|
338
|
+
parts.push({
|
|
339
|
+
type: "image",
|
|
340
|
+
source: {
|
|
341
|
+
type: "base64",
|
|
342
|
+
media_type: part.source.media_type,
|
|
343
|
+
data: part.source.data,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
content = parts;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
content = "";
|
|
352
|
+
}
|
|
221
353
|
blocks.push({
|
|
222
354
|
type: "tool_result",
|
|
223
355
|
tool_use_id: block.tool_use_id,
|
|
224
|
-
content
|
|
356
|
+
content,
|
|
357
|
+
...(block.is_error ? { is_error: true } : {}),
|
|
225
358
|
});
|
|
226
359
|
}
|
|
227
360
|
else if (block.type === "image" && block.source) {
|
|
@@ -250,6 +383,12 @@ export class AnthropicClient extends LLMClientBase {
|
|
|
250
383
|
}));
|
|
251
384
|
}
|
|
252
385
|
handleApiError(err) {
|
|
386
|
+
// ESC / Stop path — see openai.ts handleApiError for the same logic.
|
|
387
|
+
// Rethrow the SDK's abort error unchanged so server.ts recognises
|
|
388
|
+
// cancellation; don't repackage it as a generic "Anthropic API error".
|
|
389
|
+
if (err instanceof Anthropic.APIUserAbortError) {
|
|
390
|
+
throw err;
|
|
391
|
+
}
|
|
253
392
|
if (err instanceof Anthropic.APIError) {
|
|
254
393
|
if (err.status === 429) {
|
|
255
394
|
throw new LLMRateLimitError("anthropic");
|