berry-agent 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm — 单发受托管补全服务(04 §3.7 complete 单发 + §5 预算两维)。
|
|
3
|
+
*
|
|
4
|
+
* complete 是后台件(compaction 摘要、goal 轮间沉淀、记忆 review)发起单次
|
|
5
|
+
* 补全的唯一合法路径:
|
|
6
|
+
* 1. 轻量单发就是单发——不起 subagent loop(loop 装配是任务委派的成本,
|
|
7
|
+
* 不是摘要/分类的成本);
|
|
8
|
+
* 2. 复用 resolveModel + retryAssistantCall + StreamFnDefaults——与主对话
|
|
9
|
+
* 同一模型解析、重试语义与请求参数面,调用方不另立炉灶。
|
|
10
|
+
*
|
|
11
|
+
* 预算闸门(04 §5 两维——承 berry 三维删插件维,2026-09-04 立项裁决):
|
|
12
|
+
* canAfford(priority)——foreground 恒 true(用户可见请求永远放行);background
|
|
13
|
+
* 当日后台累计 tokens < 限额(缺省 4M)。执法位点 = llm 层拒发
|
|
14
|
+
* (LLM_BUDGET_EXCEEDED 拒在请求发出前);底账 = 注入的聚合查询
|
|
15
|
+
* (backgroundSpentToday 读侧)——余额不存储、重放推导(llm/usage durable
|
|
16
|
+
* 事件聚合,05 篇)。硬拒之上另有软着陆层(04 §5 预警三档):backgroundUsage
|
|
17
|
+
* 投影读面 + budgetAdvisoryLevel 档位判定(70/85/95%)供装配位铸造瞬态预警
|
|
18
|
+
* 注入文案;子代理 90% reserve 线常量同件单源(SUBAGENT_RESERVE_THRESHOLD)。
|
|
19
|
+
*/
|
|
20
|
+
import { BaseError } from '../contracts/index.js';
|
|
21
|
+
import { randomUUID } from 'node:crypto';
|
|
22
|
+
import { formatModelId } from './model-id.js';
|
|
23
|
+
import { classifyError, isContextOverflow, retryAssistantCall, } from './recovery.js';
|
|
24
|
+
/** 缺省重试策略:开 1 次重试、500ms 起步指数退避(SDK 级重试之外的有界第二层) */
|
|
25
|
+
const DEFAULT_RETRY = { enabled: true, maxRetries: 1, baseDelayMs: 500 };
|
|
26
|
+
/** 零用量(达帽错误终态合成用——同 stream-fn 的 errorStream 惯例) */
|
|
27
|
+
const NO_USAGE = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0 };
|
|
28
|
+
/** 缺省后台预算:当日后台补全 tokens(in+out 合计)限额(04 §5 起草值) */
|
|
29
|
+
const DEFAULT_BACKGROUND_BUDGET = 4_000_000;
|
|
30
|
+
/**
|
|
31
|
+
* 预警三档线(04 §5 软着陆层——达 70 / 85 / 95% 各档:NOTICE / URGENT /
|
|
32
|
+
* CRITICAL):常量可配置、不入库。exhausted 不设档——池尽归 canAfford 硬拒
|
|
33
|
+
* 与停靠腿(04 §5 成熟度 #5 实装),本层不重复造词。
|
|
34
|
+
*/
|
|
35
|
+
export const BUDGET_ADVISORY_THRESHOLDS = { notice: 0.7, urgent: 0.85, critical: 0.95 };
|
|
36
|
+
/**
|
|
37
|
+
* 非交互子代理 reserve 线(04 §5——总预算 90% 线强停子代理):留余量给
|
|
38
|
+
* 主循环写终态(子代理先耗尽则父终态写不出,实证失败模式)。执法位 =
|
|
39
|
+
* host/subagent-factory(起跑前 + 在飞轮询),本件只持常量单源。
|
|
40
|
+
*/
|
|
41
|
+
export const SUBAGENT_RESERVE_THRESHOLD = 0.9;
|
|
42
|
+
/** 档位判定(ratio → 三档;null = 未达 notice 线无预警——消费面零注入) */
|
|
43
|
+
export function budgetAdvisoryLevel(ratio) {
|
|
44
|
+
if (ratio >= BUDGET_ADVISORY_THRESHOLDS.critical)
|
|
45
|
+
return 'critical';
|
|
46
|
+
if (ratio >= BUDGET_ADVISORY_THRESHOLDS.urgent)
|
|
47
|
+
return 'urgent';
|
|
48
|
+
if (ratio >= BUDGET_ADVISORY_THRESHOLDS.notice)
|
|
49
|
+
return 'notice';
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 创建单发受托管补全服务(host 装配根 provide 的那一个对象)。
|
|
54
|
+
*/
|
|
55
|
+
export function createLlmService(options) {
|
|
56
|
+
const { runtime, defaults = {}, defaultModel, retry = DEFAULT_RETRY, tracker, hookDispatch } = options;
|
|
57
|
+
const budget = options.backgroundBudgetTokens ?? DEFAULT_BACKGROUND_BUDGET;
|
|
58
|
+
// 当日后台已耗 = 注入的聚合查询(底账 = llm/usage 事件投影,缺省无已耗)
|
|
59
|
+
const spentToday = options.backgroundSpentToday ?? (() => 0);
|
|
60
|
+
/**
|
|
61
|
+
* 预算闸门(04 §5 两维):foreground 恒 true(用户可见请求永远优先——前台
|
|
62
|
+
* 不硬断);background = 当日后台累计 < 全局限额。
|
|
63
|
+
*/
|
|
64
|
+
const canAfford = (priority) => {
|
|
65
|
+
if (priority === 'foreground')
|
|
66
|
+
return true;
|
|
67
|
+
return spentToday() < budget;
|
|
68
|
+
};
|
|
69
|
+
/** pi-ai Model → ModelInfo 投影(listModels/getModel 同一映射,同表同账) */
|
|
70
|
+
const toModelInfo = (m) => ({
|
|
71
|
+
id: formatModelId(m.provider, m.id),
|
|
72
|
+
name: m.name,
|
|
73
|
+
provider: m.provider,
|
|
74
|
+
reasoning: m.reasoning,
|
|
75
|
+
input: [...m.input],
|
|
76
|
+
contextWindow: m.contextWindow,
|
|
77
|
+
maxTokens: m.maxTokens,
|
|
78
|
+
});
|
|
79
|
+
/** 目录点查窗口:全形 id → contextWindow;缺模型 undefined(诚实退化) */
|
|
80
|
+
const contextWindowOf = (id) => {
|
|
81
|
+
const found = runtime.listModels().find((m) => formatModelId(m.provider, m.id) === id);
|
|
82
|
+
return found === undefined ? undefined : found.contextWindow;
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
registerProvider: (provider) => runtime.registerProvider(provider),
|
|
86
|
+
unregisterProvider: (id) => runtime.unregisterProvider(id),
|
|
87
|
+
// 错误桶判定:recovery 桶表直通——单源表的公开消费位
|
|
88
|
+
classifyError: (message) => classifyError(message),
|
|
89
|
+
// 溢出判定:窗口活取目录点查——缺模型 undefined 即仅错误正则一路
|
|
90
|
+
// (静默溢出/length 零输出两路天然不触发,诚实退化不阻断)
|
|
91
|
+
isContextOverflowFor: (message, model) => isContextOverflow(message, contextWindowOf(model)),
|
|
92
|
+
// 模型目录只读投影:pi-ai Model → ModelInfo 字段子集直通——id 组
|
|
93
|
+
// "provider/model-id" 全形(resolveModel 同名可解析),传输/配置面不披露
|
|
94
|
+
listModels(provider) {
|
|
95
|
+
return runtime.listModels(provider).map(toModelInfo);
|
|
96
|
+
},
|
|
97
|
+
// 点查复用同一投影(同表同账);全形 id 不在目录 = undefined(点查语义——
|
|
98
|
+
// 不抛 LLM_MODEL_NOT_FOUND,那是 resolveModel 发补全请求时的 fail-loud 面)
|
|
99
|
+
getModel(id) {
|
|
100
|
+
const found = runtime.listModels().find((m) => formatModelId(m.provider, m.id) === id);
|
|
101
|
+
return found === undefined ? undefined : toModelInfo(found);
|
|
102
|
+
},
|
|
103
|
+
canAfford,
|
|
104
|
+
// 当日后台预算投影(canAfford 同源数值面——同 spentToday/限额两单点)
|
|
105
|
+
backgroundUsage: () => {
|
|
106
|
+
const spent = spentToday();
|
|
107
|
+
return { spent, limit: budget, ratio: spent / budget };
|
|
108
|
+
},
|
|
109
|
+
async complete(req) {
|
|
110
|
+
// 钩子派发段前置查(03 §3.4——02 §5.3 LLM_CALL_IN_HOOK):钩子 handler
|
|
111
|
+
// 内 await 单发补全 = 耦合违法(fire-and-forget 尾链窗外合法);先于预算
|
|
112
|
+
// 闸/模型解析(调用方违例最先判)
|
|
113
|
+
if (hookDispatch?.inHookDispatch() === true) {
|
|
114
|
+
throw new BaseError('LLM_CALL_IN_HOOK', '钩子执行段禁模型调用(03 §3.4——钩子 handler 内 await 单发补全即耦合;起异步任务不等结果合法)');
|
|
115
|
+
}
|
|
116
|
+
// 预算闸门(04 §5 执法位点):后台调用且当日已耗尽 → 拒在请求发出前
|
|
117
|
+
// (不是事后记账发现超了——钱花出去才拒不是预算)。检查在调用前;入账在
|
|
118
|
+
// 成功后(装配层经 onUsage 落 llm/usage durable 事件)——最后一发可略超
|
|
119
|
+
// 限额(check-then-act 于单发粒度;另 write-behind 批落窗口内的最近一笔
|
|
120
|
+
// 闸门可能未见,同为「略超」语义——预算是软闸门,不是安全边界)。
|
|
121
|
+
if (req.priority === 'background' && !canAfford('background')) {
|
|
122
|
+
throw new BaseError('LLM_BUDGET_EXCEEDED', `当日后台预算已耗尽(限额 ${budget} tokens)——用户可见请求永远优先,后台任务下个周期再试`);
|
|
123
|
+
}
|
|
124
|
+
// 模型解析 fail-loud(BaseError LLM_MODEL_*);在重试环外:解析错误是
|
|
125
|
+
// 确定性的,重试无意义
|
|
126
|
+
const modelSpec = req.model ?? defaultModel();
|
|
127
|
+
const model = runtime.resolveModel(modelSpec);
|
|
128
|
+
// 标准三角色零转换直通(同 stream-fn 直通策略);单发无工具面
|
|
129
|
+
const piContext = {
|
|
130
|
+
systemPrompt: req.systemPrompt,
|
|
131
|
+
messages: req.messages,
|
|
132
|
+
};
|
|
133
|
+
// defaults 打底 → 具名 timeoutMs/signal 覆盖
|
|
134
|
+
const piOptions = {
|
|
135
|
+
...defaults,
|
|
136
|
+
...(req.timeoutMs !== undefined ? { timeoutMs: req.timeoutMs } : {}),
|
|
137
|
+
...(req.signal !== undefined ? { signal: req.signal } : {}),
|
|
138
|
+
};
|
|
139
|
+
// 单发不 loop——一次 streamSimple + 有界 transient 重试(retryAssistantCall)。
|
|
140
|
+
// 计时起点:全调用口径——含重试在内的墙钟耗时,终点在成功终态后
|
|
141
|
+
const startedAt = performance.now();
|
|
142
|
+
const message = await retryAssistantCall(async () => {
|
|
143
|
+
// 在飞帽(04 §3.6):与主对话路同源计数;达帽同拒——错误终态带
|
|
144
|
+
// errorCode,经 pi-ai 正则归 non-retryable 即刻上抛(不造排队口子)
|
|
145
|
+
const slot = tracker?.tryAcquire(model.provider) ?? null;
|
|
146
|
+
if (slot === null && tracker !== undefined) {
|
|
147
|
+
return {
|
|
148
|
+
role: 'assistant',
|
|
149
|
+
content: [],
|
|
150
|
+
usage: NO_USAGE,
|
|
151
|
+
stopReason: 'error',
|
|
152
|
+
errorMessage: `[LLM_INFLIGHT_LIMIT] 在飞请求达帽(provider=${model.provider}):过载期单发失败,调用方稍后自然重试`,
|
|
153
|
+
errorCode: 'LLM_INFLIGHT_LIMIT',
|
|
154
|
+
timestamp: Date.now(),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const stream = runtime.models.streamSimple(model, piContext, piOptions);
|
|
159
|
+
return (await stream.result());
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
slot?.release(); // result() 即流终结:单发消费面只走这一条路,finally 必达
|
|
163
|
+
}
|
|
164
|
+
}, retry, req.signal);
|
|
165
|
+
// 错误终态 → BaseError 上抛(04 §3.7:complete 是 Promise 面不是流面——
|
|
166
|
+
// 「永不抛」是 StreamFn 的流事件契约,单发面向 await 的调用方,错误
|
|
167
|
+
// 回到异常形态)
|
|
168
|
+
if (message.stopReason === 'error') {
|
|
169
|
+
throw new BaseError('LLM_COMPLETE_FAILED', message.errorMessage ?? '单发补全失败(错误终态)');
|
|
170
|
+
}
|
|
171
|
+
if (message.stopReason === 'aborted') {
|
|
172
|
+
throw new BaseError('LLM_COMPLETE_FAILED', message.errorMessage ?? '单发补全被取消(aborted)');
|
|
173
|
+
}
|
|
174
|
+
// 计量身份随结果携带:callId 供装配层落 llm/usage(settlement 幂等),
|
|
175
|
+
// priority 供事件分道(聚合只计 background),elapsedMs 供事件耗时聚合
|
|
176
|
+
const result = {
|
|
177
|
+
message,
|
|
178
|
+
usage: message.usage,
|
|
179
|
+
callId: randomUUID(),
|
|
180
|
+
priority: req.priority ?? 'foreground',
|
|
181
|
+
// 全调用耗时(performance.now 差——亚毫秒精度足够观测聚合,不取整保精度)
|
|
182
|
+
elapsedMs: performance.now() - startedAt,
|
|
183
|
+
};
|
|
184
|
+
// 计量 seam:回调异常隔离(观测面不拖垮补全结果;底账由装配层在此落 durable)
|
|
185
|
+
try {
|
|
186
|
+
options.onUsage?.(result, modelSpec);
|
|
187
|
+
}
|
|
188
|
+
catch (usageErr) {
|
|
189
|
+
// onUsage 异常隔离不变,但不再零可观测:llm/usage 是预算闸门唯一底账,
|
|
190
|
+
// 丢账必须可观测——经 onUsageError 交接线面落 warn(04 §3.7)
|
|
191
|
+
options.onUsageError?.(usageErr, { callId: result.callId, model: modelSpec });
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage → 计量桶单源映射(05 §1.1——四桶必落、cacheWrite1h/reasoning 上报
|
|
3
|
+
* 才落、totalTokens/cost 不入账)。run 路桥接与 complete 路 onUsage 共用
|
|
4
|
+
* (run-entry/scheduler-tick 旧扫退役后本函数是唯一映射位)。
|
|
5
|
+
*/
|
|
6
|
+
export function usageBucketsOf(usage) {
|
|
7
|
+
return {
|
|
8
|
+
input: usage.input,
|
|
9
|
+
output: usage.output,
|
|
10
|
+
cacheRead: usage.cacheRead,
|
|
11
|
+
cacheWrite: usage.cacheWrite,
|
|
12
|
+
...(usage.cacheWrite1h !== undefined ? { cacheWrite1h: usage.cacheWrite1h } : {}),
|
|
13
|
+
...(usage.reasoning !== undefined ? { reasoning: usage.reasoning } : {}),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm 模块公开面(L2 模型运行时;02 篇 §4.1 边表:llm → contracts 单边,
|
|
3
|
+
* pi-ai 裸导入仅本模块——07 篇栈纪律)。
|
|
4
|
+
*
|
|
5
|
+
* 桶出口 = 模型标识解析 + Models 宿主 + StreamFn 适配 + 会话层恢复零件 +
|
|
6
|
+
* 单发补全服务 + 在飞计数器 + provider 工厂面。pi-ai 的注入面类型在此再出口
|
|
7
|
+
* ——host 装配根做 persist→pi-ai 两 Store 适配时从这里取类型,不直接依赖 pi-ai。
|
|
8
|
+
*/
|
|
9
|
+
import './codes.js';
|
|
10
|
+
export { DEFAULT_MODEL_SPEC, resolveDefaultModelSpec, parseModelSpec, formatModelId, resolveModel, } from './model-id.js';
|
|
11
|
+
export { createLlmRuntime } from './runtime.js';
|
|
12
|
+
export { createStreamFn } from './stream-fn.js';
|
|
13
|
+
export { InFlightTracker, DEFAULT_MAX_INFLIGHT_PER_PROVIDER } from './inflight.js';
|
|
14
|
+
export { classifyError, diagnoseProviderFailure, isContextOverflow, isRecoverableLength, isRetryableAssistantError, retryAssistantCall, } from './recovery.js';
|
|
15
|
+
export { createLlmService, budgetAdvisoryLevel, BUDGET_ADVISORY_THRESHOLDS, SUBAGENT_RESERVE_THRESHOLD, } from './complete.js';
|
|
16
|
+
export { usageBucketsOf } from './events.js';
|
|
17
|
+
/** 虚拟键 berry-agent/llm 注入物(pi-ai provider 工厂族背书导出,03 篇 §3.2) */
|
|
18
|
+
export { providerApiFace } from './provider-face.js';
|
|
19
|
+
/**
|
|
20
|
+
* pi-ai faux provider(脚本模型工厂——host 装配根/插件层测试经本面取用;
|
|
21
|
+
* pi-ai 裸导入纪律仅本模块因此不破,llm 系测试同源)。pi-ai 主包一等同族
|
|
22
|
+
* 导出,非 test-only 附属包——再出口语义与上方类型族同律。
|
|
23
|
+
*/
|
|
24
|
+
export { fauxProvider } from '@earendil-works/pi-ai';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm — per-provider 在飞请求计数器(04 §3.6:缺省 4)。
|
|
3
|
+
*
|
|
4
|
+
* 主对话流 + complete 单发 + 子代理多驱动并发是现实形态,在飞帽 = 进程内
|
|
5
|
+
* 背压:达帽**显式拒绝**(不造排队口子——背压不排队爆仓)。
|
|
6
|
+
*
|
|
7
|
+
* 两出口同源计数(知 provider 的调用点恰两个):
|
|
8
|
+
* - createStreamFn 主对话路:达帽编码为流内 error 事件(永不抛契约保持,
|
|
9
|
+
* errorCode=LLM_INFLIGHT_LIMIT 归 transient 桶——会话层 auto-retry 退避后
|
|
10
|
+
* 槽已释放重试成功,拒绝与重试天然咬合);
|
|
11
|
+
* - complete 单发路:达帽同拒——错误终态经 pi-ai 正则归 non-retryable 即刻
|
|
12
|
+
* 上抛 LLM_COMPLETE_FAILED;过载期单发失败由调用方自然重试(记忆提取下轮
|
|
13
|
+
* / compaction 下次触发)。
|
|
14
|
+
*
|
|
15
|
+
* 释放三路保险(幂等):消费面 for-await 自然耗尽(next() 返回 done)或中途
|
|
16
|
+
* 退出(迭代 return()/throw())、不迭代的终态路径 result() finally 兜底——
|
|
17
|
+
* 任一路先到即减计数,重复调用无害。
|
|
18
|
+
*/
|
|
19
|
+
/** 在飞帽缺省值(04 §3.6:每 provider 并发上限 4) */
|
|
20
|
+
export const DEFAULT_MAX_INFLIGHT_PER_PROVIDER = 4;
|
|
21
|
+
/**
|
|
22
|
+
* per-provider 在飞计数器(host 装配根构造一份、两出口共享传入——「唯一知
|
|
23
|
+
* provider 的层」以模块内共享面形式成立,per-provider 名实相符)。
|
|
24
|
+
*/
|
|
25
|
+
export class InFlightTracker {
|
|
26
|
+
/** 每 provider 在飞上限(0 = 不限) */
|
|
27
|
+
max;
|
|
28
|
+
/** provider → 当前在飞数(0 时删键防泄漏增长) */
|
|
29
|
+
counts = new Map();
|
|
30
|
+
/** @param maxPerProvider 每 provider 并发上限(缺省 4,04 §3.6;0 = 不限) */
|
|
31
|
+
constructor(maxPerProvider = DEFAULT_MAX_INFLIGHT_PER_PROVIDER) {
|
|
32
|
+
this.max = maxPerProvider;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 尝试占一个名额(达帽返回 null = 显式拒绝信号,调用方按各自出口编码错误)。
|
|
36
|
+
* @param provider 供应商标识(模型解析所得)
|
|
37
|
+
*/
|
|
38
|
+
tryAcquire(provider) {
|
|
39
|
+
if (this.max <= 0)
|
|
40
|
+
return NOOP_SLOT; // 0 = 不限:恒成功且不计数
|
|
41
|
+
const current = this.counts.get(provider) ?? 0;
|
|
42
|
+
if (current >= this.max)
|
|
43
|
+
return null;
|
|
44
|
+
this.counts.set(provider, current + 1);
|
|
45
|
+
let released = false;
|
|
46
|
+
return {
|
|
47
|
+
release: () => {
|
|
48
|
+
if (released)
|
|
49
|
+
return; // 幂等:迭代 return() 与 result() 双路径只生效一次
|
|
50
|
+
released = true;
|
|
51
|
+
const now = this.counts.get(provider) ?? 1;
|
|
52
|
+
if (now <= 1)
|
|
53
|
+
this.counts.delete(provider);
|
|
54
|
+
else
|
|
55
|
+
this.counts.set(provider, now - 1);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** 当前某 provider 在飞数(诊断面/测试断言;不限档恒 0) */
|
|
60
|
+
inFlight(provider) {
|
|
61
|
+
return this.counts.get(provider) ?? 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** 不限档的空名额(release 无操作——不计数也不减) */
|
|
65
|
+
const NOOP_SLOT = { release: () => undefined };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm — 模型标识解析(04 §5 模型标识解析细则:model 为字符串 id,解析归 llm 模块)。
|
|
3
|
+
*
|
|
4
|
+
* 约定形式:`"provider/model-id"`,**首斜杠分割**——provider 不得含斜杠,
|
|
5
|
+
* model-id 允许再含斜杠(openrouter 路径式 id 如 `openrouter/qwen/qwen3-coder`
|
|
6
|
+
* → provider=openrouter、id=qwen/qwen3-coder)。agent loop 全程只传字符串,
|
|
7
|
+
* Model 对象在 LLM 调用边界才解析。
|
|
8
|
+
*
|
|
9
|
+
* 同文件承载缺省模型声明与 env 覆盖(04 §5 + 07 篇表 #4/#6):缺省
|
|
10
|
+
* `anthropic/claude-sonnet-5` 随包声明,BERRY_AGENT_MODEL 覆盖(用户级一次性
|
|
11
|
+
* 换脑,不落库)。
|
|
12
|
+
*/
|
|
13
|
+
import { BaseError } from '../contracts/index.js';
|
|
14
|
+
/** 缺省模型(07 篇表 #6 拍板:anthropic/claude-sonnet-5——随包声明) */
|
|
15
|
+
export const DEFAULT_MODEL_SPEC = 'anthropic/claude-sonnet-5';
|
|
16
|
+
/**
|
|
17
|
+
* 解析缺省模型标识(04 §5:BERRY_AGENT_MODEL 覆盖缺省模型)。
|
|
18
|
+
* @param env 环境变量面(缺省 process.env——测试注入)
|
|
19
|
+
* @returns 生效模型标识(env 未设/空串回落 DEFAULT_MODEL_SPEC;env 值本身的
|
|
20
|
+
* 合法性留给 resolveModel fail-loud——此处不做二次校验)
|
|
21
|
+
*/
|
|
22
|
+
export function resolveDefaultModelSpec(env = process.env) {
|
|
23
|
+
const override = env.BERRY_AGENT_MODEL;
|
|
24
|
+
return override !== undefined && override !== '' ? override : DEFAULT_MODEL_SPEC;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 解析模型标识字符串(纯语法层,不查目录)。
|
|
28
|
+
* @param spec 形如 "provider/model-id";provider 段与 id 段均须非空
|
|
29
|
+
* @throws BaseError(LLM_MODEL_SPEC_INVALID) 格式非法时
|
|
30
|
+
*/
|
|
31
|
+
export function parseModelSpec(spec) {
|
|
32
|
+
const slashIndex = spec.indexOf('/');
|
|
33
|
+
if (slashIndex <= 0 || slashIndex >= spec.length - 1) {
|
|
34
|
+
throw new BaseError('LLM_MODEL_SPEC_INVALID', `模型标识格式非法:"${spec}"——必须是 "provider/model-id" 形式(如 ${DEFAULT_MODEL_SPEC},首斜杠分割)`);
|
|
35
|
+
}
|
|
36
|
+
return { provider: spec.slice(0, slashIndex), id: spec.slice(slashIndex + 1) };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 组回标准形式(与 parseModelSpec 互逆;目录清单展示用)。
|
|
40
|
+
*/
|
|
41
|
+
export function formatModelId(provider, id) {
|
|
42
|
+
return `${provider}/${id}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 在 Models 目录中解析模型标识为具体 Model 对象(fail-loud 路径,供 UI/装配层用)。
|
|
46
|
+
* @throws BaseError(LLM_MODEL_NOT_FOUND) provider 未注册或模型不在目录时
|
|
47
|
+
*/
|
|
48
|
+
export function resolveModel(models, spec) {
|
|
49
|
+
const { provider, id } = parseModelSpec(spec);
|
|
50
|
+
if (!models.getProvider(provider)) {
|
|
51
|
+
throw new BaseError('LLM_MODEL_NOT_FOUND', `provider 未注册:${provider}(标识 "${spec}")`);
|
|
52
|
+
}
|
|
53
|
+
const model = models.getModel(provider, id);
|
|
54
|
+
if (!model) {
|
|
55
|
+
throw new BaseError('LLM_MODEL_NOT_FOUND', `模型不存在:${spec}——该 provider 目录中的模型 id 无此条目`);
|
|
56
|
+
}
|
|
57
|
+
return model;
|
|
58
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm — 虚拟键 `berry-agent/llm` 注入物(03 篇 §3.2 虚拟面六键之一:
|
|
3
|
+
* 「LLM 工厂面(pi-ai 适配的 provider API face)——模型层正路」)。
|
|
4
|
+
*
|
|
5
|
+
* provider 插件的正确路径:用**宿主同版本**的 pi-ai 工厂族造 provider,再经
|
|
6
|
+
* ctx.llm.registerProvider 入册——不自捆 pi-ai 副本(双实例 = 传输层行为分叉
|
|
7
|
+
* 的温床,03 §3.2「防双实例」纪律)、不自拼 fetch。
|
|
8
|
+
*
|
|
9
|
+
* 拓扑护栏:本面对象由 host 装配根经 jiti transform 注入插件模块说明符
|
|
10
|
+
* `berry-agent/llm`——pi-ai 裸导入纪律(仅 llm 模块)因此不破,模块 DAG 不变。
|
|
11
|
+
*/
|
|
12
|
+
import { createProvider, hasApi, lazyApi } from '@earendil-works/pi-ai';
|
|
13
|
+
/**
|
|
14
|
+
* 虚拟键注入物:pi-ai provider 工厂族再导出对象。
|
|
15
|
+
*
|
|
16
|
+
* - createProvider / hasApi(models 出口):造 provider 实例 + Model 的 api
|
|
17
|
+
* 收窄守卫——插件 registerProvider 路径的两件套;
|
|
18
|
+
* - lazyApi(api/lazy 出口):动态 api 模块包装器(首调用才加载);
|
|
19
|
+
* - anthropicMessagesApi / openAICompletionsApi(两家直取便捷键——Anthropic
|
|
20
|
+
* messages 形与 OpenAI chat-completions 形,覆盖 provider 接入面的两大主流
|
|
21
|
+
* 协议):其余 provider 的 lazy 工厂(google/…共十余家)经 pi-ai 子路径
|
|
22
|
+
* `@earendil-works/pi-ai/api/<name>.lazy` 可达——插件应优先走 createProvider
|
|
23
|
+
* 全链而非裸拿 api 流;确需扩展时按需增键(单键解决一个 provider 的诉求,
|
|
24
|
+
* 成批抄全 = 面无纪律)。
|
|
25
|
+
*/
|
|
26
|
+
export declare const providerApiFace: {
|
|
27
|
+
readonly createProvider: typeof createProvider;
|
|
28
|
+
readonly hasApi: typeof hasApi;
|
|
29
|
+
readonly lazyApi: typeof lazyApi;
|
|
30
|
+
readonly anthropicMessagesApi: () => import("@earendil-works/pi-ai").ProviderStreams;
|
|
31
|
+
readonly openAICompletionsApi: () => import("@earendil-works/pi-ai").ProviderStreams;
|
|
32
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm — 虚拟键 `berry-agent/llm` 注入物(03 篇 §3.2 虚拟面六键之一:
|
|
3
|
+
* 「LLM 工厂面(pi-ai 适配的 provider API face)——模型层正路」)。
|
|
4
|
+
*
|
|
5
|
+
* provider 插件的正确路径:用**宿主同版本**的 pi-ai 工厂族造 provider,再经
|
|
6
|
+
* ctx.llm.registerProvider 入册——不自捆 pi-ai 副本(双实例 = 传输层行为分叉
|
|
7
|
+
* 的温床,03 §3.2「防双实例」纪律)、不自拼 fetch。
|
|
8
|
+
*
|
|
9
|
+
* 拓扑护栏:本面对象由 host 装配根经 jiti transform 注入插件模块说明符
|
|
10
|
+
* `berry-agent/llm`——pi-ai 裸导入纪律(仅 llm 模块)因此不破,模块 DAG 不变。
|
|
11
|
+
*/
|
|
12
|
+
import { createProvider, hasApi, lazyApi } from '@earendil-works/pi-ai';
|
|
13
|
+
import { anthropicMessagesApi } from '@earendil-works/pi-ai/api/anthropic-messages.lazy';
|
|
14
|
+
import { openAICompletionsApi } from '@earendil-works/pi-ai/api/openai-completions.lazy';
|
|
15
|
+
/**
|
|
16
|
+
* 虚拟键注入物:pi-ai provider 工厂族再导出对象。
|
|
17
|
+
*
|
|
18
|
+
* - createProvider / hasApi(models 出口):造 provider 实例 + Model 的 api
|
|
19
|
+
* 收窄守卫——插件 registerProvider 路径的两件套;
|
|
20
|
+
* - lazyApi(api/lazy 出口):动态 api 模块包装器(首调用才加载);
|
|
21
|
+
* - anthropicMessagesApi / openAICompletionsApi(两家直取便捷键——Anthropic
|
|
22
|
+
* messages 形与 OpenAI chat-completions 形,覆盖 provider 接入面的两大主流
|
|
23
|
+
* 协议):其余 provider 的 lazy 工厂(google/…共十余家)经 pi-ai 子路径
|
|
24
|
+
* `@earendil-works/pi-ai/api/<name>.lazy` 可达——插件应优先走 createProvider
|
|
25
|
+
* 全链而非裸拿 api 流;确需扩展时按需增键(单键解决一个 provider 的诉求,
|
|
26
|
+
* 成批抄全 = 面无纪律)。
|
|
27
|
+
*/
|
|
28
|
+
export const providerApiFace = {
|
|
29
|
+
createProvider,
|
|
30
|
+
hasApi,
|
|
31
|
+
lazyApi,
|
|
32
|
+
anthropicMessagesApi,
|
|
33
|
+
openAICompletionsApi,
|
|
34
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm — 会话层恢复零件的 berry-agent 类型面包装(04 §3.5:classifyError 单源
|
|
3
|
+
* 纯函数表 + pi-ai 零件复用)。
|
|
4
|
+
*
|
|
5
|
+
* pi-ai 的 isContextOverflow / retryAssistantCall 等零件面向 pi-ai
|
|
6
|
+
* AssistantMessage 类型签名;本文件以**我们的 contracts AssistantMessage**
|
|
7
|
+
* 为签名薄包装(结构同构,边界单次收口),conversation 驱动(turn 级
|
|
8
|
+
* auto-retry 与溢出 compact-and-retry-once,04 §3.3/§3.4)从这里取用,
|
|
9
|
+
* 不直接触 pi-ai 类型。
|
|
10
|
+
*/
|
|
11
|
+
import { isContextOverflow as piIsContextOverflow, isRecoverableLength as piIsRecoverableLength, isRetryableAssistantError as piIsRetryable, retryAssistantCall as piRetryAssistantCall, } from '@earendil-works/pi-ai';
|
|
12
|
+
/** 边界收口:宿主消息 → pi-ai 签名(结构同构,超集兼容子集方向) */
|
|
13
|
+
function toPi(message) {
|
|
14
|
+
return message;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 检测上下文溢出(04 §3.4 溢出兜底的第一步)。
|
|
18
|
+
* 覆盖三类:显式报错(provider 正则)、静默溢出(input+cacheRead 超窗)、
|
|
19
|
+
* length 且零输出(截断填满型)。
|
|
20
|
+
* @param contextWindow 模型上下文窗口——传入才启用静默溢出检测
|
|
21
|
+
*/
|
|
22
|
+
export function isContextOverflow(message, contextWindow) {
|
|
23
|
+
return piIsContextOverflow(toPi(message), contextWindow);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* length 截止是否低于预期输出上限(上下文压力/供应商截断信号——
|
|
27
|
+
* 允许上层做一次有界 compact-and-retry)。
|
|
28
|
+
*/
|
|
29
|
+
export function isRecoverableLength(message, desiredMaxOutput) {
|
|
30
|
+
return piIsRecoverableLength(toPi(message), desiredMaxOutput);
|
|
31
|
+
}
|
|
32
|
+
/** 失败消息是否像 transient(网络/限流类)错误——决定 turn 级重试是否值得 */
|
|
33
|
+
export function isRetryableAssistantError(message) {
|
|
34
|
+
return piIsRetryable(toPi(message));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 错误四桶(04 §3.5 表)——词汇定义已归 contracts/llm.ts(ErrorBucket),
|
|
38
|
+
* 此处 re-export 维持 llm 公开面不变:
|
|
39
|
+
* transient/non-retryable/quota 是消费动作桶(每桶一个动作无歧义态),
|
|
40
|
+
* overflow 只分类不消费(动作挂溢出兜底纵切)。
|
|
41
|
+
* 驱动 runTurns 重试循环只消费 'transient'。
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* 配额耗尽文案子集(pi-ai retry.ts NON_RETRYABLE 正则的配额族词表——
|
|
45
|
+
* 该正则模块内私有不导出,此处同款自写)。
|
|
46
|
+
* quota 桶与 generic non-retryable 的分流:配额类失败要明示「重试治不了」
|
|
47
|
+
* 的诊断语义(04 §3.5 桶③),不是重试判定差异——两者都不进 auto-retry。
|
|
48
|
+
*/
|
|
49
|
+
const QUOTA_TEXT_PATTERN = /(GoUsageLimit|FreeUsageLimit|Monthly usage limit|available balance|insufficient_quota|out of budget|quota exceeded|billing)/i;
|
|
50
|
+
/** 错误文案的取值面:errorMessage 优先,退而 content 首文本块(与 pi-ai 判定面同源) */
|
|
51
|
+
function errorText(message) {
|
|
52
|
+
if (message.errorMessage !== undefined && message.errorMessage !== '')
|
|
53
|
+
return message.errorMessage;
|
|
54
|
+
const text = message.content.find((block) => block.type === 'text');
|
|
55
|
+
return text?.text ?? '';
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 错误桶判定(04 §3.5——单源纯函数表,llm 适配层唯一分类点,全仓无第二
|
|
59
|
+
* 分类处;判定序 2026-09-05 llm 落码批定序):errorCode 在场码优先、文案
|
|
60
|
+
* 正则兜底。判定步:
|
|
61
|
+
* ① errorCode=LLM_INFLIGHT_LIMIT(在飞帽拒绝)→ transient——并发压力自解,
|
|
62
|
+
* 退避后槽已释放;
|
|
63
|
+
* ② isContextOverflow → overflow(分类不消费);
|
|
64
|
+
* ③ 配额文案族 → quota(429/rate limit 不在此族——归 transient 桶;
|
|
65
|
+
* provider 真错误码归一挂 provider 钩子纵切,落码前 errorCode 无 provider
|
|
66
|
+
* 写点、文案正则是唯一现实路);
|
|
67
|
+
* ④ pi-ai isRetryable 正则(网络/5xx/429/overloaded/流早断)→ transient;
|
|
68
|
+
* ⑤ 其余 → non-retryable(保守:未知错误不重试)。
|
|
69
|
+
*/
|
|
70
|
+
export function classifyError(message) {
|
|
71
|
+
// ① 宿主合成码优先(errorCode 是机器判定位,摆脱 [CODE] 文本前缀约定)
|
|
72
|
+
if (message.errorCode === 'LLM_INFLIGHT_LIMIT')
|
|
73
|
+
return 'transient';
|
|
74
|
+
// ② 溢出分类位(provider 正则 + 静默溢出 + length 零输出)
|
|
75
|
+
if (piIsContextOverflow(toPi(message)))
|
|
76
|
+
return 'overflow';
|
|
77
|
+
// ③ 配额族文案 → quota(在 isRetryable 之前测:insufficient_quota 在 pi-ai
|
|
78
|
+
// 归 non-retryable,此处细分到 quota 桶供诊断面区分)
|
|
79
|
+
if (QUOTA_TEXT_PATTERN.test(errorText(message)))
|
|
80
|
+
return 'quota';
|
|
81
|
+
// ④ transient 正则(429/5xx/网络/流早断)
|
|
82
|
+
if (piIsRetryable(toPi(message)))
|
|
83
|
+
return 'transient';
|
|
84
|
+
// ⑤ 保守默认:未知即不可重试(auth/参数/内容策略类重试只会再失败)
|
|
85
|
+
return 'non-retryable';
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 鉴权失败文案族(401/403/invalid api key 族——provider 报文人间千姿,
|
|
89
|
+
* 正则面兜大类;provider 真错误码归一挂 provider 钩子纵切落码前,文案
|
|
90
|
+
* 正则是唯一现实判位——与错误桶表 quota 族判据同境况)。
|
|
91
|
+
*/
|
|
92
|
+
const AUTH_TEXT_PATTERN = /(\b401\b|\b403\b|invalid[^\n]{0,40}api.?key|incorrect api key|api key not (?:valid|found)|unauthorized|authentication|permission denied)/i;
|
|
93
|
+
/** 上游报文附注截断帽(与 RunOutcome.error 同幅——≤200 字符人读够用) */
|
|
94
|
+
const UPSTREAM_NOTE_CAP = 200;
|
|
95
|
+
/** 模型标识 → provider 名('provider/model-id' 前段;无斜杠回退原串) */
|
|
96
|
+
function providerNameOf(modelSpec) {
|
|
97
|
+
const slash = modelSpec.indexOf('/');
|
|
98
|
+
return slash > 0 ? modelSpec.slice(0, slash) : modelSpec;
|
|
99
|
+
}
|
|
100
|
+
/** 上游报文附注(超帽截断加省略号——原文降附注律) */
|
|
101
|
+
function upstreamNote(text) {
|
|
102
|
+
return text.length > UPSTREAM_NOTE_CAP ? `${text.slice(0, UPSTREAM_NOTE_CAP)}…` : text;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* provider 失败形态识别(07 §5——与错误桶表同源居本模块;纯函数零 IO)。
|
|
106
|
+
* 判据两形态:
|
|
107
|
+
* - unconfigured:宿主合成码 LLM_MODEL_NOT_FOUND / LLM_MODEL_SPEC_INVALID
|
|
108
|
+
* (stream-fn resolveModel 失败——provider 未注册或目录无此模型;errorCode
|
|
109
|
+
* 与 `[CODE]` 文案前缀双在位,任一判位命中即识——run settle 只透传
|
|
110
|
+
* errorMessage 时文案判位兜底);
|
|
111
|
+
* - auth:错误文案正则。
|
|
112
|
+
* 非两形态回 undefined——调用方原文直出(transient/quota 族已有桶语义,
|
|
113
|
+
* 不在本面越俎)。
|
|
114
|
+
*/
|
|
115
|
+
export function diagnoseProviderFailure(failure, modelSpec) {
|
|
116
|
+
const text = failure.errorMessage ?? '';
|
|
117
|
+
if (failure.errorCode === 'LLM_MODEL_NOT_FOUND' ||
|
|
118
|
+
failure.errorCode === 'LLM_MODEL_SPEC_INVALID' ||
|
|
119
|
+
text.includes('[LLM_MODEL_NOT_FOUND]') ||
|
|
120
|
+
text.includes('[LLM_MODEL_SPEC_INVALID]')) {
|
|
121
|
+
return {
|
|
122
|
+
kind: 'unconfigured',
|
|
123
|
+
hint: `模型不可用:${modelSpec}——provider 未注册或目录中无此模型。` +
|
|
124
|
+
`检查模型标识拼写(形如 provider/model-id);更换缺省模型设 BERRY_AGENT_MODEL 环境变量。` +
|
|
125
|
+
`上游报文:${upstreamNote(text)}`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (text !== '' && AUTH_TEXT_PATTERN.test(text)) {
|
|
129
|
+
return {
|
|
130
|
+
kind: 'auth',
|
|
131
|
+
hint: `provider 鉴权失败(${providerNameOf(modelSpec)}):请配置该 provider 的 API 凭证——` +
|
|
132
|
+
`对应环境变量(如 ANTHROPIC_API_KEY / OPENAI_API_KEY)或数据目录凭证表。` +
|
|
133
|
+
`上游报文:${upstreamNote(text)}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* 单次 assistant 产出的有界重试(04 §3.7 complete 单发的轻量重试零件;
|
|
140
|
+
* pi 原用途挂 compaction 摘要旁路)。abort 归一为 aborted 消息、非可重试
|
|
141
|
+
* 错误立即返回、成功即返回——语义细节见 pi-ai retry.ts 文档注释。
|
|
142
|
+
*/
|
|
143
|
+
export async function retryAssistantCall(produce, policy, signal, callbacks) {
|
|
144
|
+
const final = await piRetryAssistantCall(async () => toPi(await produce()), policy, signal, callbacks);
|
|
145
|
+
return final;
|
|
146
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* llm — Models 宿主包装(04 §5 模型层:llm 模块 = pi-ai Models 接口的宿主包装 +
|
|
3
|
+
* StreamFn 适配)。
|
|
4
|
+
*
|
|
5
|
+
* 职责边界:
|
|
6
|
+
* - 包装 pi-ai `createModels`(凭证/模型目录经注入——persist 的 credentials/
|
|
7
|
+
* model_catalog 两面由 host 装配根适配后注入;llm 不 import persist,
|
|
8
|
+
* 02 篇边表 llm → contracts 单边);
|
|
9
|
+
* - 内置 provider 全家桶默认注册(Anthropic-first 体现在缺省模型约定与文档,
|
|
10
|
+
* 不在 provider 注册面做裁剪——未配置凭证的 provider 天然不可用);
|
|
11
|
+
* - `registerProvider` 即插件钩 `ctx.llm.registerProvider` 的实现底座(03 篇
|
|
12
|
+
* §2.2 注册动词表);`berry-agent/llm` 虚拟键的注入物(providerApiFace)
|
|
13
|
+
* 与本面同源——插件用宿主同版本的工厂造 provider 入册,防双实例(03 §3.2)。
|
|
14
|
+
*/
|
|
15
|
+
import { builtinProviders } from '@earendil-works/pi-ai/providers/all';
|
|
16
|
+
import { createModels } from '@earendil-works/pi-ai';
|
|
17
|
+
import { resolveModel } from './model-id.js';
|
|
18
|
+
/**
|
|
19
|
+
* 创建 llm 运行时(宿主包装本体)。
|
|
20
|
+
* 不做网络访问:createModels 只装配;目录刷新是显式 refresh 调用。
|
|
21
|
+
*/
|
|
22
|
+
export function createLlmRuntime(options = {}) {
|
|
23
|
+
const models = createModels({
|
|
24
|
+
credentials: options.credentials,
|
|
25
|
+
modelsStore: options.modelsStore,
|
|
26
|
+
});
|
|
27
|
+
for (const provider of options.providers ?? builtinProviders()) {
|
|
28
|
+
// 按 id upsert——测试/装配显式传入的集合整体生效,不与内置目录叠加
|
|
29
|
+
models.setProvider(provider);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
models,
|
|
33
|
+
resolveModel: (spec) => resolveModel(models, spec),
|
|
34
|
+
listModels: (provider) => models.getModels(provider),
|
|
35
|
+
refresh: (refreshOptions) => models.refresh(refreshOptions),
|
|
36
|
+
checkAuth: (providerId, checkOptions) => models.checkAuth(providerId, checkOptions),
|
|
37
|
+
registerProvider(provider) {
|
|
38
|
+
models.setProvider(provider);
|
|
39
|
+
return () => {
|
|
40
|
+
// 简单移除语义:不比对是否仍是本 provider(卸载期竞态由宿主装载序串行化)
|
|
41
|
+
models.deleteProvider(provider.id);
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
unregisterProvider: (id) => models.deleteProvider(id),
|
|
45
|
+
};
|
|
46
|
+
}
|