@armadra/agent 0.0.0-stage → 0.2.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 +22 -0
- package/LICENSE +21 -0
- package/README.md +426 -2
- package/dist/agent/agent.d.ts +56 -0
- package/dist/agent/agent.js +129 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +158 -0
- package/dist/agent/queue.d.ts +25 -0
- package/dist/agent/queue.js +55 -0
- package/dist/agent/retry.d.ts +22 -0
- package/dist/agent/retry.js +92 -0
- package/dist/agent/schema.d.ts +27 -0
- package/dist/agent/schema.js +186 -0
- package/dist/agent/session-cache.d.ts +100 -0
- package/dist/agent/session-cache.js +511 -0
- package/dist/agent/session-compaction.d.ts +49 -0
- package/dist/agent/session-compaction.js +251 -0
- package/dist/agent/session-core.d.ts +103 -0
- package/dist/agent/session-core.js +7 -0
- package/dist/agent/session-run.d.ts +55 -0
- package/dist/agent/session-run.js +246 -0
- package/dist/agent/session-state.d.ts +39 -0
- package/dist/agent/session-state.js +118 -0
- package/dist/agent/session-subagent.d.ts +54 -0
- package/dist/agent/session-subagent.js +157 -0
- package/dist/agent/session-sync.d.ts +20 -0
- package/dist/agent/session-sync.js +104 -0
- package/dist/agent/session-tools.d.ts +24 -0
- package/dist/agent/session-tools.js +277 -0
- package/dist/agent/session.d.ts +117 -0
- package/dist/agent/session.js +484 -0
- package/dist/agent/system-prompt.d.ts +49 -0
- package/dist/agent/system-prompt.js +121 -0
- package/dist/agent/tool-runner.d.ts +64 -0
- package/dist/agent/tool-runner.js +309 -0
- package/dist/agent/transform.d.ts +26 -0
- package/dist/agent/transform.js +171 -0
- package/dist/agent/types.d.ts +378 -0
- package/dist/agent/types.js +21 -0
- package/dist/ai/apis/anthropic-messages.d.ts +26 -0
- package/dist/ai/apis/anthropic-messages.js +214 -0
- package/dist/ai/apis/anthropic-request.d.ts +42 -0
- package/dist/ai/apis/anthropic-request.js +282 -0
- package/dist/ai/apis/api.d.ts +28 -0
- package/dist/ai/apis/api.js +102 -0
- package/dist/ai/apis/cache-params.d.ts +48 -0
- package/dist/ai/apis/cache-params.js +163 -0
- package/dist/ai/apis/google-generative-ai.d.ts +32 -0
- package/dist/ai/apis/google-generative-ai.js +249 -0
- package/dist/ai/apis/google-request.d.ts +40 -0
- package/dist/ai/apis/google-request.js +255 -0
- package/dist/ai/apis/openai-compat.d.ts +35 -0
- package/dist/ai/apis/openai-compat.js +142 -0
- package/dist/ai/apis/openai-completions.d.ts +28 -0
- package/dist/ai/apis/openai-completions.js +246 -0
- package/dist/ai/apis/openai-request.d.ts +35 -0
- package/dist/ai/apis/openai-request.js +303 -0
- package/dist/ai/apis/openai-responses-request.d.ts +52 -0
- package/dist/ai/apis/openai-responses-request.js +266 -0
- package/dist/ai/apis/openai-responses.d.ts +28 -0
- package/dist/ai/apis/openai-responses.js +303 -0
- package/dist/ai/apis/shared.d.ts +56 -0
- package/dist/ai/apis/shared.js +179 -0
- package/dist/ai/cache/economics.d.ts +24 -0
- package/dist/ai/cache/economics.js +69 -0
- package/dist/ai/cache/fingerprint.d.ts +17 -0
- package/dist/ai/cache/fingerprint.js +39 -0
- package/dist/ai/cache/miss.d.ts +49 -0
- package/dist/ai/cache/miss.js +96 -0
- package/dist/ai/cache/reporting.d.ts +39 -0
- package/dist/ai/cache/reporting.js +102 -0
- package/dist/ai/cache/types.d.ts +94 -0
- package/dist/ai/cache/types.js +19 -0
- package/dist/ai/cache/warmer.d.ts +94 -0
- package/dist/ai/cache/warmer.js +224 -0
- package/dist/ai/context.d.ts +35 -0
- package/dist/ai/context.js +114 -0
- package/dist/ai/cost.d.ts +21 -0
- package/dist/ai/cost.js +60 -0
- package/dist/ai/event-stream.d.ts +33 -0
- package/dist/ai/event-stream.js +101 -0
- package/dist/ai/fake/fake-provider.d.ts +68 -0
- package/dist/ai/fake/fake-provider.js +278 -0
- package/dist/ai/fake/fake-script.d.ts +69 -0
- package/dist/ai/fake/fake-script.js +142 -0
- package/dist/ai/http.d.ts +54 -0
- package/dist/ai/http.js +212 -0
- package/dist/ai/json-partial.d.ts +17 -0
- package/dist/ai/json-partial.js +243 -0
- package/dist/ai/overflow.d.ts +22 -0
- package/dist/ai/overflow.js +61 -0
- package/dist/ai/providers/auth.d.ts +71 -0
- package/dist/ai/providers/auth.js +202 -0
- package/dist/ai/providers/builtin.d.ts +23 -0
- package/dist/ai/providers/builtin.js +148 -0
- package/dist/ai/providers/catalog-data.d.ts +5 -0
- package/dist/ai/providers/catalog-data.js +19 -0
- package/dist/ai/providers/catalog.d.ts +28 -0
- package/dist/ai/providers/catalog.js +166 -0
- package/dist/ai/providers/registry.d.ts +83 -0
- package/dist/ai/providers/registry.js +305 -0
- package/dist/ai/sse.d.ts +32 -0
- package/dist/ai/sse.js +140 -0
- package/dist/ai/thinking.d.ts +42 -0
- package/dist/ai/thinking.js +101 -0
- package/dist/ai/types.d.ts +428 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +25770 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +82 -0
- package/dist/cli/args.js +381 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +398 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +104 -0
- package/dist/cli/compose-session.d.ts +69 -0
- package/dist/cli/compose-session.js +370 -0
- package/dist/cli/compose-store.d.ts +24 -0
- package/dist/cli/compose-store.js +125 -0
- package/dist/cli/compose.d.ts +106 -0
- package/dist/cli/compose.js +206 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +205 -0
- package/dist/cli/deps.js +12 -0
- package/dist/cli/exit-codes.d.ts +29 -0
- package/dist/cli/exit-codes.js +40 -0
- package/dist/cli/main.d.ts +26 -0
- package/dist/cli/main.js +165 -0
- package/dist/cli/runtime.d.ts +90 -0
- package/dist/cli/runtime.js +8 -0
- package/dist/cli/startup-screen.d.ts +15 -0
- package/dist/cli/startup-screen.js +56 -0
- package/dist/cli/startup-steps.d.ts +35 -0
- package/dist/cli/startup-steps.js +197 -0
- package/dist/cli/subcommands/auth.d.ts +12 -0
- package/dist/cli/subcommands/auth.js +91 -0
- package/dist/cli/subcommands/config.d.ts +44 -0
- package/dist/cli/subcommands/config.js +201 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +41 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -0
- package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
- package/dist/cli/subcommands/models-cache-probe.js +190 -0
- package/dist/cli/subcommands/models-discover.d.ts +40 -0
- package/dist/cli/subcommands/models-discover.js +204 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +144 -0
- package/dist/cli/subcommands/sessions.d.ts +11 -0
- package/dist/cli/subcommands/sessions.js +100 -0
- package/dist/codemode/capability.d.ts +37 -0
- package/dist/codemode/capability.js +55 -0
- package/dist/codemode/declarations.d.ts +44 -0
- package/dist/codemode/declarations.js +111 -0
- package/dist/codemode/host-side.d.ts +59 -0
- package/dist/codemode/host-side.js +253 -0
- package/dist/codemode/modes.d.ts +23 -0
- package/dist/codemode/modes.js +40 -0
- package/dist/codemode/protocol.d.ts +91 -0
- package/dist/codemode/protocol.js +114 -0
- package/dist/codemode/sandbox-entry.d.ts +25 -0
- package/dist/codemode/sandbox-entry.js +323 -0
- package/dist/codemode/store.d.ts +26 -0
- package/dist/codemode/store.js +53 -0
- package/dist/codemode/tool.d.ts +105 -0
- package/dist/codemode/tool.js +283 -0
- package/dist/compaction/branch-summary.d.ts +30 -0
- package/dist/compaction/branch-summary.js +79 -0
- package/dist/compaction/breaker.d.ts +32 -0
- package/dist/compaction/breaker.js +65 -0
- package/dist/compaction/cut-point.d.ts +23 -0
- package/dist/compaction/cut-point.js +70 -0
- package/dist/compaction/estimate.d.ts +30 -0
- package/dist/compaction/estimate.js +114 -0
- package/dist/compaction/prune-tier.d.ts +31 -0
- package/dist/compaction/prune-tier.js +83 -0
- package/dist/compaction/serialize.d.ts +20 -0
- package/dist/compaction/serialize.js +115 -0
- package/dist/compaction/summarize-tier.d.ts +111 -0
- package/dist/compaction/summarize-tier.js +305 -0
- package/dist/config/auth-file.d.ts +39 -0
- package/dist/config/auth-file.js +92 -0
- package/dist/config/context-files.d.ts +30 -0
- package/dist/config/context-files.js +77 -0
- package/dist/config/load.d.ts +45 -0
- package/dist/config/load.js +97 -0
- package/dist/config/merge.d.ts +73 -0
- package/dist/config/merge.js +248 -0
- package/dist/config/paths.d.ts +38 -0
- package/dist/config/paths.js +108 -0
- package/dist/config/profile.d.ts +27 -0
- package/dist/config/profile.js +65 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.js +413 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +172 -0
- package/dist/config/types.js +27 -0
- package/dist/config/write.d.ts +10 -0
- package/dist/config/write.js +25 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +30 -0
- package/dist/hooks/config.d.ts +49 -0
- package/dist/hooks/config.js +102 -0
- package/dist/hooks/dispatcher.d.ts +41 -0
- package/dist/hooks/dispatcher.js +104 -0
- package/dist/hooks/matcher.d.ts +22 -0
- package/dist/hooks/matcher.js +108 -0
- package/dist/hooks/protocol.d.ts +33 -0
- package/dist/hooks/protocol.js +240 -0
- package/dist/hooks/runner.d.ts +30 -0
- package/dist/hooks/runner.js +143 -0
- package/dist/hooks/types.d.ts +147 -0
- package/dist/hooks/types.js +20 -0
- package/dist/host/api-impl.d.ts +75 -0
- package/dist/host/api-impl.js +173 -0
- package/dist/host/loader.d.ts +31 -0
- package/dist/host/loader.js +147 -0
- package/dist/host/types.d.ts +169 -0
- package/dist/host/types.js +14 -0
- package/dist/host.d.ts +5 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +28 -0
- package/dist/modes/commands-core.d.ts +44 -0
- package/dist/modes/commands-core.js +171 -0
- package/dist/modes/interactive/approval-dialog.d.ts +48 -0
- package/dist/modes/interactive/approval-dialog.js +202 -0
- package/dist/modes/interactive/commands.d.ts +41 -0
- package/dist/modes/interactive/commands.js +202 -0
- package/dist/modes/interactive/completion.d.ts +53 -0
- package/dist/modes/interactive/completion.js +157 -0
- package/dist/modes/interactive/interactive-mode.d.ts +46 -0
- package/dist/modes/interactive/interactive-mode.js +487 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -0
- package/dist/modes/interactive/key-dispatch.js +107 -0
- package/dist/modes/interactive/line/line-editor.d.ts +50 -0
- package/dist/modes/interactive/line/line-editor.js +232 -0
- package/dist/modes/interactive/line/line-mode.d.ts +20 -0
- package/dist/modes/interactive/line/line-mode.js +216 -0
- package/dist/modes/interactive/line/line-render.d.ts +35 -0
- package/dist/modes/interactive/line/line-render.js +171 -0
- package/dist/modes/interactive/line/paste-state.d.ts +26 -0
- package/dist/modes/interactive/line/paste-state.js +72 -0
- package/dist/modes/interactive/message-view.d.ts +77 -0
- package/dist/modes/interactive/message-view.js +269 -0
- package/dist/modes/interactive/pickers.d.ts +42 -0
- package/dist/modes/interactive/pickers.js +115 -0
- package/dist/modes/interactive/startup-ui.d.ts +28 -0
- package/dist/modes/interactive/startup-ui.js +238 -0
- package/dist/modes/interactive/status-bar.d.ts +48 -0
- package/dist/modes/interactive/status-bar.js +139 -0
- package/dist/modes/interactive/tool-view.d.ts +95 -0
- package/dist/modes/interactive/tool-view.js +344 -0
- package/dist/modes/print/json-event.d.ts +15 -0
- package/dist/modes/print/json-event.js +39 -0
- package/dist/modes/print/print-mode.d.ts +15 -0
- package/dist/modes/print/print-mode.js +96 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +258 -0
- package/dist/modes/rpc/jsonl.d.ts +16 -0
- package/dist/modes/rpc/jsonl.js +55 -0
- package/dist/modes/rpc/rpc-mode.d.ts +22 -0
- package/dist/modes/rpc/rpc-mode.js +153 -0
- package/dist/modes/session-report.d.ts +55 -0
- package/dist/modes/session-report.js +278 -0
- package/dist/modes/shared.d.ts +13 -0
- package/dist/modes/shared.js +52 -0
- package/dist/modes/startup-ui-text.d.ts +16 -0
- package/dist/modes/startup-ui-text.js +103 -0
- package/dist/permissions/broker.d.ts +47 -0
- package/dist/permissions/broker.js +107 -0
- package/dist/permissions/dangerous.d.ts +47 -0
- package/dist/permissions/dangerous.js +370 -0
- package/dist/permissions/pipeline.d.ts +51 -0
- package/dist/permissions/pipeline.js +181 -0
- package/dist/permissions/preview.d.ts +43 -0
- package/dist/permissions/preview.js +463 -0
- package/dist/permissions/rules.d.ts +58 -0
- package/dist/permissions/rules.js +249 -0
- package/dist/permissions/types.d.ts +100 -0
- package/dist/permissions/types.js +18 -0
- package/dist/rpc.d.ts +171 -0
- package/dist/rpc.js +13 -0
- package/dist/sdk.d.ts +98 -0
- package/dist/sdk.js +195 -0
- package/dist/session/manager.d.ts +70 -0
- package/dist/session/manager.js +278 -0
- package/dist/session/migrate.d.ts +19 -0
- package/dist/session/migrate.js +53 -0
- package/dist/session/projection.d.ts +50 -0
- package/dist/session/projection.js +180 -0
- package/dist/session/store.d.ts +47 -0
- package/dist/session/store.js +210 -0
- package/dist/session/tree.d.ts +21 -0
- package/dist/session/tree.js +88 -0
- package/dist/session/types.d.ts +199 -0
- package/dist/session/types.js +18 -0
- package/dist/skills/discover.d.ts +60 -0
- package/dist/skills/discover.js +160 -0
- package/dist/skills/expand.d.ts +35 -0
- package/dist/skills/expand.js +46 -0
- package/dist/skills/frontmatter.d.ts +27 -0
- package/dist/skills/frontmatter.js +157 -0
- package/dist/skills/index-prompt.d.ts +13 -0
- package/dist/skills/index-prompt.js +42 -0
- package/dist/skills/templates.d.ts +52 -0
- package/dist/skills/templates.js +177 -0
- package/dist/tools/bash.d.ts +49 -0
- package/dist/tools/bash.js +222 -0
- package/dist/tools/edit-fuzzy.d.ts +36 -0
- package/dist/tools/edit-fuzzy.js +129 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +241 -0
- package/dist/tools/file-mutex.d.ts +8 -0
- package/dist/tools/file-mutex.js +29 -0
- package/dist/tools/glob.d.ts +44 -0
- package/dist/tools/glob.js +217 -0
- package/dist/tools/grep.d.ts +39 -0
- package/dist/tools/grep.js +179 -0
- package/dist/tools/ignore.d.ts +40 -0
- package/dist/tools/ignore.js +174 -0
- package/dist/tools/ls.d.ts +21 -0
- package/dist/tools/ls.js +96 -0
- package/dist/tools/output-accumulator.d.ts +48 -0
- package/dist/tools/output-accumulator.js +106 -0
- package/dist/tools/paths.d.ts +20 -0
- package/dist/tools/paths.js +58 -0
- package/dist/tools/presets.d.ts +71 -0
- package/dist/tools/presets.js +128 -0
- package/dist/tools/process-tree.d.ts +35 -0
- package/dist/tools/process-tree.js +125 -0
- package/dist/tools/read.d.ts +28 -0
- package/dist/tools/read.js +181 -0
- package/dist/tools/registry.d.ts +43 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/shell.d.ts +38 -0
- package/dist/tools/shell.js +91 -0
- package/dist/tools/task.d.ts +29 -0
- package/dist/tools/task.js +107 -0
- package/dist/tools/todo.d.ts +28 -0
- package/dist/tools/todo.js +107 -0
- package/dist/tools/truncate.d.ts +44 -0
- package/dist/tools/truncate.js +175 -0
- package/dist/tools/types.d.ts +135 -0
- package/dist/tools/types.js +19 -0
- package/dist/tools/write.d.ts +20 -0
- package/dist/tools/write.js +84 -0
- package/dist/tui/ansi.d.ts +29 -0
- package/dist/tui/ansi.js +420 -0
- package/dist/tui/component.d.ts +38 -0
- package/dist/tui/component.js +13 -0
- package/dist/tui/components/box.d.ts +26 -0
- package/dist/tui/components/box.js +65 -0
- package/dist/tui/components/container.d.ts +14 -0
- package/dist/tui/components/container.js +39 -0
- package/dist/tui/components/editor-buffer.d.ts +67 -0
- package/dist/tui/components/editor-buffer.js +300 -0
- package/dist/tui/components/editor-paste.d.ts +29 -0
- package/dist/tui/components/editor-paste.js +64 -0
- package/dist/tui/components/editor.d.ts +114 -0
- package/dist/tui/components/editor.js +530 -0
- package/dist/tui/components/key-value.d.ts +29 -0
- package/dist/tui/components/key-value.js +48 -0
- package/dist/tui/components/loader.d.ts +35 -0
- package/dist/tui/components/loader.js +71 -0
- package/dist/tui/components/markdown.d.ts +90 -0
- package/dist/tui/components/markdown.js +462 -0
- package/dist/tui/components/meter.d.ts +29 -0
- package/dist/tui/components/meter.js +47 -0
- package/dist/tui/components/overlay.d.ts +24 -0
- package/dist/tui/components/overlay.js +52 -0
- package/dist/tui/components/select-list.d.ts +57 -0
- package/dist/tui/components/select-list.js +179 -0
- package/dist/tui/components/spacer.d.ts +11 -0
- package/dist/tui/components/spacer.js +16 -0
- package/dist/tui/components/text.d.ts +30 -0
- package/dist/tui/components/text.js +65 -0
- package/dist/tui/keybindings.d.ts +66 -0
- package/dist/tui/keybindings.js +120 -0
- package/dist/tui/keys.d.ts +38 -0
- package/dist/tui/keys.js +275 -0
- package/dist/tui/stdin-buffer.d.ts +41 -0
- package/dist/tui/stdin-buffer.js +206 -0
- package/dist/tui/terminal.d.ts +79 -0
- package/dist/tui/terminal.js +153 -0
- package/dist/tui/theme.d.ts +31 -0
- package/dist/tui/theme.js +199 -0
- package/dist/tui/tui.d.ts +103 -0
- package/dist/tui/tui.js +363 -0
- package/dist/tui/vt-screen.d.ts +46 -0
- package/dist/tui/vt-screen.js +251 -0
- package/dist/tui.d.ts +30 -0
- package/dist/tui.js +31 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +24 -0
- package/docs/codemode.md +102 -0
- package/docs/hooks.md +196 -0
- package/docs/host-api.md +161 -0
- package/docs/providers.md +316 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +168 -0
- package/package.json +101 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama models cache-probe <provider/model>`(第三波 §1.11):这个端点报不报缓存。[W3-C2]
|
|
3
|
+
*
|
|
4
|
+
* 固定前缀(system 一段确定性的编号句子,按字符 / 4 估到 `--tokens`,缺省 2048)+ user
|
|
5
|
+
* `Reply with: ok`,`maxTokens` 16、`cacheRetention: "short"`、`purpose: "probe"`,相隔 `--gap-ms`
|
|
6
|
+
* 发两次。判定:第二次 cacheRead ≥ 第一次前缀的 50% → `reported`;两次读写都是 0 → `silent`
|
|
7
|
+
* (字段缺失或恒 0);其余(读到一点、只有写入)→ `inconclusive`(粒度或 TTL 问题)。
|
|
8
|
+
*
|
|
9
|
+
* 计费动作:执行前打印预估(两次 × `--tokens` × 目录输入价,无价 `$?`);非 TTY 必须 `--yes`
|
|
10
|
+
* (否则退出 2),TTY 下问一次 y/N。`--json` 时预估写 stderr,stdout 只有一个结果对象。
|
|
11
|
+
* 协议层不保留原始响应,「usage 字段」列的是该协议解析时读取的字段名。
|
|
12
|
+
*/
|
|
13
|
+
import type { Api, Model, Usage } from "../../ai/types.js";
|
|
14
|
+
import type { ModelsAction } from "./models.js";
|
|
15
|
+
export declare const PROBE_DEFAULT_TOKENS = 2048;
|
|
16
|
+
export declare const PROBE_DEFAULT_GAP_MS = 3000;
|
|
17
|
+
export declare const PROBE_REQUEST_TIMEOUT_MS = 60000;
|
|
18
|
+
export type ProbeVerdict = "reported" | "silent" | "inconclusive";
|
|
19
|
+
export interface ProbeSample {
|
|
20
|
+
input: number;
|
|
21
|
+
cacheRead: number;
|
|
22
|
+
cacheWrite: number;
|
|
23
|
+
/** 原始响应里出现过缓存字段(含 0);协议没给出时 undefined。 */
|
|
24
|
+
cacheReported: boolean | undefined;
|
|
25
|
+
promptTokens: number;
|
|
26
|
+
}
|
|
27
|
+
/** 各协议解析缓存用量时读取的字段(`parseOpenAIUsage` 等)。 */
|
|
28
|
+
export declare const USAGE_FIELDS: Readonly<Partial<Record<Api, readonly string[]>>>;
|
|
29
|
+
/** 确定性前缀:编号句子,字符数 ≈ tokens × 4。 */
|
|
30
|
+
export declare function probePrefix(tokens: number): string;
|
|
31
|
+
export declare function sampleOf(usage: Usage): ProbeSample;
|
|
32
|
+
export declare function judgeProbe(first: ProbeSample, second: ProbeSample): ProbeVerdict;
|
|
33
|
+
export declare function probeAdvice(verdict: ProbeVerdict, model: Model, samples?: readonly ProbeSample[]): string | undefined;
|
|
34
|
+
export declare const CACHE_PROBE_ACTION: ModelsAction;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama models cache-probe <provider/model>`(第三波 §1.11):这个端点报不报缓存。[W3-C2]
|
|
3
|
+
*
|
|
4
|
+
* 固定前缀(system 一段确定性的编号句子,按字符 / 4 估到 `--tokens`,缺省 2048)+ user
|
|
5
|
+
* `Reply with: ok`,`maxTokens` 16、`cacheRetention: "short"`、`purpose: "probe"`,相隔 `--gap-ms`
|
|
6
|
+
* 发两次。判定:第二次 cacheRead ≥ 第一次前缀的 50% → `reported`;两次读写都是 0 → `silent`
|
|
7
|
+
* (字段缺失或恒 0);其余(读到一点、只有写入)→ `inconclusive`(粒度或 TTL 问题)。
|
|
8
|
+
*
|
|
9
|
+
* 计费动作:执行前打印预估(两次 × `--tokens` × 目录输入价,无价 `$?`);非 TTY 必须 `--yes`
|
|
10
|
+
* (否则退出 2),TTY 下问一次 y/N。`--json` 时预估写 stderr,stdout 只有一个结果对象。
|
|
11
|
+
* 协议层不保留原始响应,「usage 字段」列的是该协议解析时读取的字段名。
|
|
12
|
+
*/
|
|
13
|
+
import { createInterface } from "node:readline/promises";
|
|
14
|
+
import { priceTokens } from "../../ai/cost.js";
|
|
15
|
+
import { UsageError } from "../args.js";
|
|
16
|
+
import { ExitCode } from "../exit-codes.js";
|
|
17
|
+
export const PROBE_DEFAULT_TOKENS = 2048;
|
|
18
|
+
export const PROBE_DEFAULT_GAP_MS = 3000;
|
|
19
|
+
export const PROBE_REQUEST_TIMEOUT_MS = 60_000;
|
|
20
|
+
/** 各协议解析缓存用量时读取的字段(`parseOpenAIUsage` 等)。 */
|
|
21
|
+
export const USAGE_FIELDS = {
|
|
22
|
+
"openai-completions": [
|
|
23
|
+
"prompt_tokens_details.cached_tokens",
|
|
24
|
+
"prompt_cache_hit_tokens",
|
|
25
|
+
"cached_tokens",
|
|
26
|
+
"prompt_tokens_details.cache_write_tokens",
|
|
27
|
+
],
|
|
28
|
+
"openai-responses": ["input_tokens_details.cached_tokens"],
|
|
29
|
+
"anthropic-messages": ["cache_read_input_tokens", "cache_creation_input_tokens"],
|
|
30
|
+
"google-generative-ai": ["usageMetadata.cachedContentTokenCount"],
|
|
31
|
+
};
|
|
32
|
+
/** 确定性前缀:编号句子,字符数 ≈ tokens × 4。 */
|
|
33
|
+
export function probePrefix(tokens) {
|
|
34
|
+
const lines = [];
|
|
35
|
+
let chars = 0;
|
|
36
|
+
for (let i = 1; chars < tokens * 4; i++) {
|
|
37
|
+
const line = `Reference line ${i}: cache probe filler text that stays byte-identical between requests.`;
|
|
38
|
+
lines.push(line);
|
|
39
|
+
chars += line.length + 1;
|
|
40
|
+
}
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
export function sampleOf(usage) {
|
|
44
|
+
return {
|
|
45
|
+
input: usage.input,
|
|
46
|
+
cacheRead: usage.cacheRead,
|
|
47
|
+
cacheWrite: usage.cacheWrite,
|
|
48
|
+
cacheReported: usage.cacheReported,
|
|
49
|
+
promptTokens: usage.input + usage.cacheRead + usage.cacheWrite,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function judgeProbe(first, second) {
|
|
53
|
+
if (first.promptTokens > 0 && second.cacheRead >= first.promptTokens * 0.5)
|
|
54
|
+
return "reported";
|
|
55
|
+
const any = first.cacheRead + first.cacheWrite + second.cacheRead + second.cacheWrite;
|
|
56
|
+
return any === 0 ? "silent" : "inconclusive";
|
|
57
|
+
}
|
|
58
|
+
export function probeAdvice(verdict, model, samples = []) {
|
|
59
|
+
if (verdict === "silent") {
|
|
60
|
+
// 实测:同一中转的 Kimi 间隔 3 s 两次都是 0、间隔更久才命中——字段存在时提示写入延迟的可能
|
|
61
|
+
const present = samples.some((s) => s.cacheReported === true)
|
|
62
|
+
? "(响应里有缓存字段但恒为 0;也可能是缓存写入有延迟,可加大 --gap-ms 再测一次)"
|
|
63
|
+
: "";
|
|
64
|
+
return `可在 config 里设 providers.${model.provider}.compat.cacheReporting: "silent",状态栏将显示未报告${present}`;
|
|
65
|
+
}
|
|
66
|
+
if (verdict === "inconclusive")
|
|
67
|
+
return "第二次只读到少量缓存或只有写入:可能是缓存粒度或 TTL 问题,可加大 --tokens 或缩短 --gap-ms 重试";
|
|
68
|
+
if (model.promptCache === undefined)
|
|
69
|
+
return `可自填 promptCache.short 以启用保温(providers.${model.provider}.modelOverrides: [{ "id": "${model.id}", "promptCache": { "short": 300 } }])`;
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
function intOption(ctx, name, fallback, min) {
|
|
73
|
+
const raw = ctx.values.get(name);
|
|
74
|
+
if (raw === undefined)
|
|
75
|
+
return fallback;
|
|
76
|
+
const value = Number(raw);
|
|
77
|
+
if (!Number.isInteger(value) || value < min)
|
|
78
|
+
throw new UsageError(`--${name} 应为不小于 ${min} 的整数`);
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
async function confirm() {
|
|
82
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
83
|
+
try {
|
|
84
|
+
return /^y(es)?$/i.test((await rl.question("继续?[y/N] ")).trim());
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
rl.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function sampleLine(index, s) {
|
|
91
|
+
const field = s.cacheReported === undefined ? "?" : s.cacheReported ? "有" : "无";
|
|
92
|
+
return `#${index} input ${s.input} · cacheRead ${s.cacheRead} · cacheWrite ${s.cacheWrite} · 缓存字段 ${field}`;
|
|
93
|
+
}
|
|
94
|
+
async function run(ctx) {
|
|
95
|
+
const { io, registry } = ctx;
|
|
96
|
+
const ref = ctx.args[0] ?? "";
|
|
97
|
+
const tokens = intOption(ctx, "tokens", PROBE_DEFAULT_TOKENS, 1);
|
|
98
|
+
const gapMs = intOption(ctx, "gap-ms", PROBE_DEFAULT_GAP_MS, 0);
|
|
99
|
+
const json = ctx.flags.has("json");
|
|
100
|
+
const found = registry.findModel(ref);
|
|
101
|
+
if (!found.ok) {
|
|
102
|
+
io.stderr(`ama: 模型不存在:${ref}\n`);
|
|
103
|
+
return ExitCode.NoModel;
|
|
104
|
+
}
|
|
105
|
+
const { model, provider } = found;
|
|
106
|
+
const key = await registry.resolveApiKey(provider.id);
|
|
107
|
+
if (key.apiKey === undefined && provider.requiresApiKey) {
|
|
108
|
+
io.stderr(`ama: ${provider.id} 没有 API key(ama auth set ${provider.id})\n`);
|
|
109
|
+
return ExitCode.NoModel;
|
|
110
|
+
}
|
|
111
|
+
const api = registry.getApi(model.api);
|
|
112
|
+
if (api === undefined) {
|
|
113
|
+
io.stderr(`ama: 协议 ${model.api} 尚未实现\n`);
|
|
114
|
+
return ExitCode.RuntimeError;
|
|
115
|
+
}
|
|
116
|
+
const estimate = priceTokens(model, { input: tokens * 2, output: 32 });
|
|
117
|
+
const name = `${provider.id}/${model.id}`;
|
|
118
|
+
const head = `cache-probe ${name}(${model.api})· 前缀约 ${tokens} token · 间隔 ${gapMs} ms\n预估花费:${estimate === undefined ? "$?" : `$${estimate.toFixed(4)}`}(两次 × ${tokens} token × 目录价)\n`;
|
|
119
|
+
(json ? io.stderr : io.stdout)(head);
|
|
120
|
+
if (!ctx.flags.has("yes")) {
|
|
121
|
+
if (!io.stdinIsTTY) {
|
|
122
|
+
io.stderr("ama: cache-probe 会发两次计费请求,非交互环境需加 --yes\n");
|
|
123
|
+
return ExitCode.Usage;
|
|
124
|
+
}
|
|
125
|
+
if (!(await confirm())) {
|
|
126
|
+
io.stderr("ama: 已取消\n");
|
|
127
|
+
return ExitCode.Ok;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const messages = [
|
|
131
|
+
{ role: "system", sections: { probe: probePrefix(tokens) }, timestamp: 0 },
|
|
132
|
+
{ role: "user", content: "Reply with: ok", timestamp: 0 },
|
|
133
|
+
];
|
|
134
|
+
const samples = [];
|
|
135
|
+
for (let i = 0; i < 2; i++) {
|
|
136
|
+
if (i > 0 && gapMs > 0)
|
|
137
|
+
await new Promise((r) => setTimeout(r, gapMs));
|
|
138
|
+
const message = await api
|
|
139
|
+
.stream(model, { messages }, {
|
|
140
|
+
signal: AbortSignal.timeout(PROBE_REQUEST_TIMEOUT_MS),
|
|
141
|
+
...(key.apiKey !== undefined ? { apiKey: key.apiKey } : {}),
|
|
142
|
+
maxTokens: 16,
|
|
143
|
+
cacheRetention: "short",
|
|
144
|
+
purpose: "probe",
|
|
145
|
+
})
|
|
146
|
+
.result();
|
|
147
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
148
|
+
io.stderr(`ama: 第 ${i + 1} 次请求失败:${message.errorMessage ?? message.stopReason}\n`);
|
|
149
|
+
return ExitCode.RuntimeError;
|
|
150
|
+
}
|
|
151
|
+
samples.push(sampleOf(message.usage));
|
|
152
|
+
}
|
|
153
|
+
const [first, second] = samples;
|
|
154
|
+
const verdict = judgeProbe(first, second);
|
|
155
|
+
const advice = probeAdvice(verdict, model, samples);
|
|
156
|
+
const fields = USAGE_FIELDS[model.api] ?? [];
|
|
157
|
+
if (json) {
|
|
158
|
+
const result = {
|
|
159
|
+
model: name,
|
|
160
|
+
api: model.api,
|
|
161
|
+
tokens,
|
|
162
|
+
gapMs,
|
|
163
|
+
estimatedCostUsd: estimate ?? null,
|
|
164
|
+
requests: samples,
|
|
165
|
+
usageFields: fields,
|
|
166
|
+
verdict,
|
|
167
|
+
...(advice !== undefined ? { advice } : {}),
|
|
168
|
+
};
|
|
169
|
+
io.stdout(`${JSON.stringify(result)}\n`);
|
|
170
|
+
return ExitCode.Ok;
|
|
171
|
+
}
|
|
172
|
+
const share = first.promptTokens > 0
|
|
173
|
+
? `(第二次读到前缀的 ${Math.round((second.cacheRead / first.promptTokens) * 100)}%)`
|
|
174
|
+
: "";
|
|
175
|
+
io.stdout([
|
|
176
|
+
sampleLine(1, first),
|
|
177
|
+
sampleLine(2, second),
|
|
178
|
+
...(fields.length > 0 ? [`usage 字段(${model.api} 读取):${fields.join(" / ")}`] : []),
|
|
179
|
+
`判定:${verdict}${share}`,
|
|
180
|
+
...(advice !== undefined ? [`建议:${advice}`] : []),
|
|
181
|
+
].join("\n") + "\n");
|
|
182
|
+
return ExitCode.Ok;
|
|
183
|
+
}
|
|
184
|
+
export const CACHE_PROBE_ACTION = {
|
|
185
|
+
usage: "ama models cache-probe <provider/id> [--tokens 2048] [--gap-ms 3000] [--json] [--yes]",
|
|
186
|
+
required: "<provider/id>",
|
|
187
|
+
valueOptions: ["tokens", "gap-ms"],
|
|
188
|
+
flagOptions: ["json", "yes"],
|
|
189
|
+
run,
|
|
190
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama models discover <provider>`(第三波 §2.3):问中转站有哪些模型。[W3-B12]
|
|
3
|
+
*
|
|
4
|
+
* GET `{baseUrl}/models`(anthropic-messages 且 baseUrl 不以 `/v1` 结尾时为 `{baseUrl}/v1/models`),
|
|
5
|
+
* 带供应商的鉴权头,打印 id 列表并标出已配置的条目。
|
|
6
|
+
*
|
|
7
|
+
* `--probe`:同一中转下不同模型支持的协议不同,对每个 id 依次试供应商协议 → completions →
|
|
8
|
+
* responses → messages(去重),各发一次最小请求(`ama models check` 同款,maxTokens 16),
|
|
9
|
+
* 记第一个成功的协议;每模型最多 3 次请求,`--limit`(缺省 30)限制探测的模型数,执行前打印
|
|
10
|
+
* 预估;401 / 403 / 429 立即停止(key 无效或被限流,继续只会浪费请求)。
|
|
11
|
+
*
|
|
12
|
+
* `--write`:新条目合并进用户级 config.json 的 `providers.<id>.models`——已有同 id 不覆盖;只写
|
|
13
|
+
* `id` 与探到的 `api`(与供应商协议相同时省略);带 `--probe` 时只写探测成功的模型。不猜
|
|
14
|
+
* `contextWindow`(自动压缩随之关闭,输出 warning)。写前备份为 `config.json.bak`。
|
|
15
|
+
*/
|
|
16
|
+
import type { Api, Model, ProviderData, ProviderRegistryApi } from "../../ai/types.js";
|
|
17
|
+
import type { ModelsAction } from "./models.js";
|
|
18
|
+
export declare const DISCOVER_TIMEOUT_MS = 15000;
|
|
19
|
+
export declare const PROBE_TIMEOUT_MS = 30000;
|
|
20
|
+
export declare const DEFAULT_PROBE_LIMIT = 30;
|
|
21
|
+
/** 中转常见的三种协议,按此顺序探测(供应商协议排最前)。 */
|
|
22
|
+
export declare const PROBE_APIS: readonly Api[];
|
|
23
|
+
/** 模型列表的 URL:OpenAI 系是 `{baseUrl}/models`;Anthropic 官方形状的 baseUrl 不带 `/v1`。 */
|
|
24
|
+
export declare function modelsUrl(provider: ProviderData): string;
|
|
25
|
+
export declare function discoverModels(provider: ProviderData, apiKey: string | undefined, options?: {
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}): Promise<Model[]>;
|
|
28
|
+
export declare function probeOrder(provider: ProviderData): Api[];
|
|
29
|
+
export interface ProbeOptions {
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
/** 每次请求后回调。 */
|
|
32
|
+
onAttempt?: (id: string, api: Api, error: string | undefined) => void;
|
|
33
|
+
/** 每个模型探完回调(进度输出)。 */
|
|
34
|
+
onResult?: (id: string, api: Api | undefined) => void;
|
|
35
|
+
/** 因鉴权失败或限流提前停止时回调。 */
|
|
36
|
+
onStop?: (reason: string) => void;
|
|
37
|
+
}
|
|
38
|
+
/** 返回已探测模型 → 第一个成功的协议(都失败为 undefined);超出 `limit` 或提前停止的不在表里。 */
|
|
39
|
+
export declare function probeModelApis(registry: ProviderRegistryApi, provider: ProviderData, ids: string[], limit: number, options?: ProbeOptions): Promise<Map<string, Api | undefined>>;
|
|
40
|
+
export declare const DISCOVER_ACTION: ModelsAction;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama models discover <provider>`(第三波 §2.3):问中转站有哪些模型。[W3-B12]
|
|
3
|
+
*
|
|
4
|
+
* GET `{baseUrl}/models`(anthropic-messages 且 baseUrl 不以 `/v1` 结尾时为 `{baseUrl}/v1/models`),
|
|
5
|
+
* 带供应商的鉴权头,打印 id 列表并标出已配置的条目。
|
|
6
|
+
*
|
|
7
|
+
* `--probe`:同一中转下不同模型支持的协议不同,对每个 id 依次试供应商协议 → completions →
|
|
8
|
+
* responses → messages(去重),各发一次最小请求(`ama models check` 同款,maxTokens 16),
|
|
9
|
+
* 记第一个成功的协议;每模型最多 3 次请求,`--limit`(缺省 30)限制探测的模型数,执行前打印
|
|
10
|
+
* 预估;401 / 403 / 429 立即停止(key 无效或被限流,继续只会浪费请求)。
|
|
11
|
+
*
|
|
12
|
+
* `--write`:新条目合并进用户级 config.json 的 `providers.<id>.models`——已有同 id 不覆盖;只写
|
|
13
|
+
* `id` 与探到的 `api`(与供应商协议相同时省略);带 `--probe` 时只写探测成功的模型。不猜
|
|
14
|
+
* `contextWindow`(自动压缩随之关闭,输出 warning)。写前备份为 `config.json.bak`。
|
|
15
|
+
*/
|
|
16
|
+
import { authHeaders, mergeHeaders } from "../../ai/http.js";
|
|
17
|
+
import { discoverLocalModels, materializeModel } from "../../ai/providers/registry.js";
|
|
18
|
+
import { withCustomDefaults } from "../../ai/providers/catalog.js";
|
|
19
|
+
import { existsSync } from "node:fs";
|
|
20
|
+
import { loadConfigFile } from "../../config/load.js";
|
|
21
|
+
import { writeConfigFile } from "../../config/write.js";
|
|
22
|
+
import { UsageError } from "../args.js";
|
|
23
|
+
import { ExitCode } from "../exit-codes.js";
|
|
24
|
+
export const DISCOVER_TIMEOUT_MS = 15_000;
|
|
25
|
+
export const PROBE_TIMEOUT_MS = 30_000;
|
|
26
|
+
export const DEFAULT_PROBE_LIMIT = 30;
|
|
27
|
+
const ANTHROPIC_VERSION = "2023-06-01";
|
|
28
|
+
/** 中转常见的三种协议,按此顺序探测(供应商协议排最前)。 */
|
|
29
|
+
export const PROBE_APIS = [
|
|
30
|
+
"openai-completions",
|
|
31
|
+
"openai-responses",
|
|
32
|
+
"anthropic-messages",
|
|
33
|
+
];
|
|
34
|
+
/** 模型列表的 URL:OpenAI 系是 `{baseUrl}/models`;Anthropic 官方形状的 baseUrl 不带 `/v1`。 */
|
|
35
|
+
export function modelsUrl(provider) {
|
|
36
|
+
const base = provider.baseUrl.replace(/\/+$/, "");
|
|
37
|
+
if (provider.api === "anthropic-messages" && !/\/v1$/.test(base))
|
|
38
|
+
return `${base}/v1/models`;
|
|
39
|
+
return `${base}/models`;
|
|
40
|
+
}
|
|
41
|
+
export async function discoverModels(provider, apiKey, options = {}) {
|
|
42
|
+
const timeoutMs = options.timeoutMs ?? DISCOVER_TIMEOUT_MS;
|
|
43
|
+
if (!provider.requiresApiKey && apiKey === undefined)
|
|
44
|
+
return discoverLocalModels(provider, { timeoutMs });
|
|
45
|
+
const anthropic = provider.api === "anthropic-messages";
|
|
46
|
+
const headers = mergeHeaders(provider.headers, anthropic ? { "anthropic-version": ANTHROPIC_VERSION } : {}, authHeaders(apiKey, provider.authHeader, anthropic ? "x-api-key" : "authorization-bearer"));
|
|
47
|
+
return discoverLocalModels(provider, { timeoutMs, url: modelsUrl(provider), headers });
|
|
48
|
+
}
|
|
49
|
+
export function probeOrder(provider) {
|
|
50
|
+
return PROBE_APIS.includes(provider.api)
|
|
51
|
+
? [provider.api, ...PROBE_APIS.filter((api) => api !== provider.api)]
|
|
52
|
+
: [...PROBE_APIS];
|
|
53
|
+
}
|
|
54
|
+
const FATAL_STATUS = /^(?:HTTP )?(?:401|403|429)\b/;
|
|
55
|
+
/** 返回已探测模型 → 第一个成功的协议(都失败为 undefined);超出 `limit` 或提前停止的不在表里。 */
|
|
56
|
+
export async function probeModelApis(registry, provider, ids, limit, options = {}) {
|
|
57
|
+
const result = new Map();
|
|
58
|
+
const key = await registry.resolveApiKey(provider.id);
|
|
59
|
+
const order = probeOrder(provider).filter((api) => registry.getApi(api) !== undefined);
|
|
60
|
+
for (const id of ids.slice(0, Math.max(0, limit))) {
|
|
61
|
+
const known = provider.models.find((m) => m.id === id);
|
|
62
|
+
let found;
|
|
63
|
+
for (const api of order) {
|
|
64
|
+
const model = known !== undefined
|
|
65
|
+
? { ...known, api }
|
|
66
|
+
: materializeModel(withCustomDefaults({ id }, provider.id, api), provider);
|
|
67
|
+
const error = await attempt(registry, model, key.apiKey, options.timeoutMs);
|
|
68
|
+
options.onAttempt?.(id, api, error);
|
|
69
|
+
if (error === undefined) {
|
|
70
|
+
found = api;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
if (FATAL_STATUS.test(error)) {
|
|
74
|
+
options.onStop?.(error);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
result.set(id, found);
|
|
79
|
+
options.onResult?.(id, found);
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
/** 一次最小请求;成功返回 undefined,失败返回错误文本。 */
|
|
84
|
+
async function attempt(registry, model, apiKey, timeoutMs = PROBE_TIMEOUT_MS) {
|
|
85
|
+
const impl = registry.getApi(model.api);
|
|
86
|
+
if (impl === undefined)
|
|
87
|
+
return `协议 ${model.api} 尚未实现`;
|
|
88
|
+
try {
|
|
89
|
+
const message = await impl
|
|
90
|
+
.stream(model, { messages: [{ role: "user", content: "Reply with: ok", timestamp: Date.now() }] }, {
|
|
91
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
92
|
+
...(apiKey !== undefined ? { apiKey } : {}),
|
|
93
|
+
maxTokens: 16,
|
|
94
|
+
cacheRetention: "none",
|
|
95
|
+
})
|
|
96
|
+
.result();
|
|
97
|
+
if (message.stopReason === "error" || message.stopReason === "aborted")
|
|
98
|
+
return message.errorMessage ?? message.stopReason;
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return error instanceof Error ? error.message : String(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function parseLimit(raw) {
|
|
106
|
+
if (raw === undefined)
|
|
107
|
+
return DEFAULT_PROBE_LIMIT;
|
|
108
|
+
const n = Number(raw);
|
|
109
|
+
if (!Number.isInteger(n) || n < 1)
|
|
110
|
+
throw new UsageError(`--limit 需要正整数:${raw}`);
|
|
111
|
+
return n;
|
|
112
|
+
}
|
|
113
|
+
/** 把新条目合并进用户级 config.json;返回写入条数。 */
|
|
114
|
+
function writeEntries(ctx, provider, entries) {
|
|
115
|
+
const { io } = ctx;
|
|
116
|
+
const path = ctx.level.userConfigPath;
|
|
117
|
+
const config = structuredClone(loadConfigFile("config", path)?.value ?? { version: 1 });
|
|
118
|
+
const providers = (config.providers ??= {});
|
|
119
|
+
if (providers[provider.id] === undefined && !provider.builtin) {
|
|
120
|
+
io.stderr(`ama: ${provider.id} 不在用户级配置(${path})里,未写入\n`);
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
const target = (providers[provider.id] ??= {});
|
|
124
|
+
const models = (target.models ??= []);
|
|
125
|
+
const existing = new Set([...models.map((m) => m.id), ...provider.models.map((m) => m.id)]);
|
|
126
|
+
const added = entries.filter((entry) => !existing.has(entry.id));
|
|
127
|
+
const kept = entries.length - added.length;
|
|
128
|
+
if (added.length === 0) {
|
|
129
|
+
io.stdout(`\n没有新模型要写入${kept > 0 ? `(${kept} 个已存在,未覆盖)` : ""}\n`);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
models.push(...added);
|
|
133
|
+
const backup = existsSync(path);
|
|
134
|
+
writeConfigFile(path, config, { backup: true });
|
|
135
|
+
io.stdout(`\n已写入 ${path}:${provider.id} 新增 ${added.length} 个模型` +
|
|
136
|
+
`${kept > 0 ? `,${kept} 个已存在未覆盖` : ""}${backup ? `(原文件备份为 ${path}.bak)` : ""}\n`);
|
|
137
|
+
io.stderr(`ama: 警告:新增模型未设 contextWindow,自动压缩关闭;需要时在 config.json 里补上\n`);
|
|
138
|
+
return added.length;
|
|
139
|
+
}
|
|
140
|
+
async function run(ctx) {
|
|
141
|
+
const { io, registry } = ctx;
|
|
142
|
+
const id = ctx.args[0] ?? "";
|
|
143
|
+
const limit = parseLimit(ctx.values.get("limit"));
|
|
144
|
+
const provider = registry.get(id);
|
|
145
|
+
if (provider === undefined) {
|
|
146
|
+
io.stderr(`ama: 供应商不存在:${id}\n`);
|
|
147
|
+
return ExitCode.NoModel;
|
|
148
|
+
}
|
|
149
|
+
const key = await registry.resolveApiKey(provider.id);
|
|
150
|
+
if (key.apiKey === undefined && provider.requiresApiKey) {
|
|
151
|
+
io.stderr(`ama: ${provider.id} 没有 API key(ama auth set ${provider.id})\n`);
|
|
152
|
+
return ExitCode.NoModel;
|
|
153
|
+
}
|
|
154
|
+
let found;
|
|
155
|
+
try {
|
|
156
|
+
found = await discoverModels(provider, key.apiKey);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
io.stderr(`ama: ${provider.id} 模型列表获取失败:${error.message}\n`);
|
|
160
|
+
return ExitCode.RuntimeError;
|
|
161
|
+
}
|
|
162
|
+
const configured = new Map(provider.models.map((m) => [m.id, m]));
|
|
163
|
+
io.stdout(`${provider.id}:发现 ${found.length} 个模型(${modelsUrl(provider)})\n`);
|
|
164
|
+
for (const model of found) {
|
|
165
|
+
const known = configured.get(model.id);
|
|
166
|
+
io.stdout(` ${model.id}${known !== undefined ? ` 已配置(${known.api})` : ""}\n`);
|
|
167
|
+
}
|
|
168
|
+
const write = ctx.flags.has("write");
|
|
169
|
+
if (!ctx.flags.has("probe")) {
|
|
170
|
+
if (write)
|
|
171
|
+
writeEntries(ctx, provider, found.map((m) => ({ id: m.id })));
|
|
172
|
+
return ExitCode.Ok;
|
|
173
|
+
}
|
|
174
|
+
const ids = found.map((m) => m.id);
|
|
175
|
+
const count = Math.min(limit, ids.length);
|
|
176
|
+
const order = probeOrder(provider);
|
|
177
|
+
io.stdout(`\n探测协议:${count} 个模型(${order.join(" → ")}),最多 ${count * order.length} 次请求` +
|
|
178
|
+
`${ids.length > count ? `;另有 ${ids.length - count} 个超出 --limit ${limit},未探测` : ""}\n`);
|
|
179
|
+
let stopped;
|
|
180
|
+
const probed = await probeModelApis(registry, provider, ids, limit, {
|
|
181
|
+
onResult: (modelId, api) => io.stdout(` ${modelId} ${api ?? "不可用(三种协议均失败)"}\n`),
|
|
182
|
+
onStop: (reason) => (stopped = reason),
|
|
183
|
+
});
|
|
184
|
+
if (write) {
|
|
185
|
+
const entries = [];
|
|
186
|
+
for (const [modelId, api] of probed) {
|
|
187
|
+
if (api !== undefined)
|
|
188
|
+
entries.push(api === provider.api ? { id: modelId } : { id: modelId, api });
|
|
189
|
+
}
|
|
190
|
+
writeEntries(ctx, provider, entries);
|
|
191
|
+
}
|
|
192
|
+
if (stopped !== undefined) {
|
|
193
|
+
io.stderr(`ama: 探测提前停止(${stopped})\n`);
|
|
194
|
+
return ExitCode.RuntimeError;
|
|
195
|
+
}
|
|
196
|
+
return ExitCode.Ok;
|
|
197
|
+
}
|
|
198
|
+
export const DISCOVER_ACTION = {
|
|
199
|
+
usage: "ama models discover <provider> [--probe] [--write] [--limit <n>]",
|
|
200
|
+
required: "<provider>",
|
|
201
|
+
valueOptions: ["limit"],
|
|
202
|
+
flagOptions: ["probe", "write"],
|
|
203
|
+
run,
|
|
204
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama models list [--provider <id>]`、`ama models check <provider/id>`(设计 §1.2、R6)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 注册表经 RuntimeDeps.providers 注入(B1)。check 发一次最小调用(一条 user 消息、
|
|
5
|
+
* maxTokens 16、超时 30 s),只报告成败与 stopReason,不输出 key。
|
|
6
|
+
*
|
|
7
|
+
* [W3-C0] 子命令分派改为动作表 `MODELS_ACTIONS`(第三波 §1.11 / §2.3):新动作(C2 的
|
|
8
|
+
* `cache-probe`、B12 的 `discover`)各写在自己的文件里,这里只加一行表项;用法文本、
|
|
9
|
+
* 选项表与必填位置参数都从表里来。
|
|
10
|
+
*/
|
|
11
|
+
import type { ProviderRegistryApi } from "../../ai/types.js";
|
|
12
|
+
import type { CliIo, RuntimeDeps } from "../deps.js";
|
|
13
|
+
import { type UserLevel } from "./context.js";
|
|
14
|
+
/** 动作执行时拿到的上下文(参数已按表解析、注册表已构造)。 */
|
|
15
|
+
export interface ModelsActionContext {
|
|
16
|
+
io: CliIo;
|
|
17
|
+
registry: ProviderRegistryApi;
|
|
18
|
+
/** 用户级(+ profile)配置;写配置的动作(`discover --write`)要用 `userConfigPath`。 */
|
|
19
|
+
level: UserLevel;
|
|
20
|
+
/** 动作名之后的位置参数。 */
|
|
21
|
+
args: readonly string[];
|
|
22
|
+
values: ReadonlyMap<string, string>;
|
|
23
|
+
flags: ReadonlySet<string>;
|
|
24
|
+
}
|
|
25
|
+
export interface ModelsAction {
|
|
26
|
+
/** 用法行(不含「ama models 」前缀之前的「用法:」)。 */
|
|
27
|
+
usage: string;
|
|
28
|
+
/** 带值选项(不含共用的 --profile / --auth-file)。 */
|
|
29
|
+
valueOptions?: readonly string[];
|
|
30
|
+
/** 布尔选项。 */
|
|
31
|
+
flagOptions?: readonly string[];
|
|
32
|
+
/** 必填的第一个位置参数,缺失时报「ama models <动作> 需要 <required>」。 */
|
|
33
|
+
required?: string;
|
|
34
|
+
run(ctx: ModelsActionContext): Promise<number>;
|
|
35
|
+
}
|
|
36
|
+
export declare const CHECK_TIMEOUT_MS = 30000;
|
|
37
|
+
/** 动作表:键是子命令名,顺序即用法文本顺序。 */
|
|
38
|
+
export declare const MODELS_ACTIONS: Readonly<Record<string, ModelsAction>>;
|
|
39
|
+
export declare const MODELS_USAGE: string;
|
|
40
|
+
export declare function runModels(argv: readonly string[], io: CliIo, deps: Pick<RuntimeDeps, "providers"> | undefined): Promise<number>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama models list [--provider <id>]`、`ama models check <provider/id>`(设计 §1.2、R6)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 注册表经 RuntimeDeps.providers 注入(B1)。check 发一次最小调用(一条 user 消息、
|
|
5
|
+
* maxTokens 16、超时 30 s),只报告成败与 stopReason,不输出 key。
|
|
6
|
+
*
|
|
7
|
+
* [W3-C0] 子命令分派改为动作表 `MODELS_ACTIONS`(第三波 §1.11 / §2.3):新动作(C2 的
|
|
8
|
+
* `cache-probe`、B12 的 `discover`)各写在自己的文件里,这里只加一行表项;用法文本、
|
|
9
|
+
* 选项表与必填位置参数都从表里来。
|
|
10
|
+
*/
|
|
11
|
+
import { parseSubArgs, UsageError } from "../args.js";
|
|
12
|
+
import { ExitCode } from "../exit-codes.js";
|
|
13
|
+
import { buildRegistry, loadUserLevel } from "./context.js";
|
|
14
|
+
import { CACHE_PROBE_ACTION } from "./models-cache-probe.js";
|
|
15
|
+
import { DISCOVER_ACTION } from "./models-discover.js";
|
|
16
|
+
const COMMON_VALUE_OPTIONS = ["profile", "auth-file"];
|
|
17
|
+
export const CHECK_TIMEOUT_MS = 30_000;
|
|
18
|
+
function compact(n) {
|
|
19
|
+
if (n === undefined)
|
|
20
|
+
return "?";
|
|
21
|
+
if (n >= 1_000_000)
|
|
22
|
+
return `${Math.round(n / 100_000) / 10}M`;
|
|
23
|
+
if (n >= 1000)
|
|
24
|
+
return `${Math.round(n / 1000)}k`;
|
|
25
|
+
return String(n);
|
|
26
|
+
}
|
|
27
|
+
async function list(io, registry, only) {
|
|
28
|
+
const providers = registry.list().filter((p) => only === undefined || p.id === only);
|
|
29
|
+
if (only !== undefined && providers.length === 0) {
|
|
30
|
+
io.stderr(`ama: 供应商不存在:${only}\n`);
|
|
31
|
+
return ExitCode.NoModel;
|
|
32
|
+
}
|
|
33
|
+
for (const provider of providers) {
|
|
34
|
+
const key = await registry.resolveApiKey(provider.id);
|
|
35
|
+
const keyText = key.apiKey !== undefined
|
|
36
|
+
? `key:${key.source}${key.origin !== undefined ? `(${key.origin})` : ""}`
|
|
37
|
+
: provider.requiresApiKey
|
|
38
|
+
? "无 key"
|
|
39
|
+
: "无需 key";
|
|
40
|
+
io.stdout(`${provider.id} ${provider.api} ${keyText}\n`);
|
|
41
|
+
for (const model of provider.models) {
|
|
42
|
+
const flags = [
|
|
43
|
+
`ctx ${compact(model.contextWindow)}`,
|
|
44
|
+
`out ${compact(model.maxTokens)}`,
|
|
45
|
+
model.reasoning ? "思考" : undefined,
|
|
46
|
+
model.input.includes("image") ? "图片" : undefined,
|
|
47
|
+
].filter((x) => x !== undefined);
|
|
48
|
+
io.stdout(` ${provider.id}/${model.id} ${flags.join(" · ")}\n`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return ExitCode.Ok;
|
|
52
|
+
}
|
|
53
|
+
async function check(io, registry, ref) {
|
|
54
|
+
const found = registry.findModel(ref);
|
|
55
|
+
if (!found.ok) {
|
|
56
|
+
const hint = found.candidates.length > 0 ? `;候选:${found.candidates.slice(0, 10).join(", ")}` : "";
|
|
57
|
+
io.stderr(`ama: 模型不存在:${ref}${hint}\n`);
|
|
58
|
+
return ExitCode.NoModel;
|
|
59
|
+
}
|
|
60
|
+
const { model, provider } = found;
|
|
61
|
+
const key = await registry.resolveApiKey(provider.id);
|
|
62
|
+
if (key.apiKey === undefined && provider.requiresApiKey) {
|
|
63
|
+
io.stderr(`ama: ${provider.id} 没有 API key(ama auth set ${provider.id})\n`);
|
|
64
|
+
return ExitCode.NoModel;
|
|
65
|
+
}
|
|
66
|
+
const api = registry.getApi(model.api);
|
|
67
|
+
if (api === undefined) {
|
|
68
|
+
io.stderr(`ama: 协议 ${model.api} 尚未实现\n`);
|
|
69
|
+
return ExitCode.RuntimeError;
|
|
70
|
+
}
|
|
71
|
+
const started = Date.now();
|
|
72
|
+
const stream = api.stream(model, { messages: [{ role: "user", content: "Reply with: ok", timestamp: Date.now() }] }, {
|
|
73
|
+
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS),
|
|
74
|
+
...(key.apiKey !== undefined ? { apiKey: key.apiKey } : {}),
|
|
75
|
+
maxTokens: 16,
|
|
76
|
+
cacheRetention: "none",
|
|
77
|
+
});
|
|
78
|
+
const message = await stream.result();
|
|
79
|
+
const ms = Date.now() - started;
|
|
80
|
+
if (message.stopReason === "error" || message.stopReason === "aborted") {
|
|
81
|
+
io.stderr(`ama: ${provider.id}/${model.id} 失败(${ms} ms):${message.errorMessage ?? message.stopReason}\n`);
|
|
82
|
+
return ExitCode.RuntimeError;
|
|
83
|
+
}
|
|
84
|
+
io.stdout(`${provider.id}/${model.id} 可用(${ms} ms,stopReason ${message.stopReason})\n`);
|
|
85
|
+
return ExitCode.Ok;
|
|
86
|
+
}
|
|
87
|
+
/** 动作表:键是子命令名,顺序即用法文本顺序。 */
|
|
88
|
+
export const MODELS_ACTIONS = Object.freeze({
|
|
89
|
+
list: {
|
|
90
|
+
usage: "ama models list [--provider <id>]",
|
|
91
|
+
valueOptions: ["provider"],
|
|
92
|
+
run: (ctx) => list(ctx.io, ctx.registry, ctx.values.get("provider")),
|
|
93
|
+
},
|
|
94
|
+
check: {
|
|
95
|
+
usage: "ama models check <provider/id>",
|
|
96
|
+
required: "<provider/id>",
|
|
97
|
+
run: (ctx) => check(ctx.io, ctx.registry, ctx.args[0] ?? ""),
|
|
98
|
+
},
|
|
99
|
+
discover: DISCOVER_ACTION,
|
|
100
|
+
"cache-probe": CACHE_PROBE_ACTION,
|
|
101
|
+
});
|
|
102
|
+
export const MODELS_USAGE = `用法:${Object.values(MODELS_ACTIONS)
|
|
103
|
+
.map((a) => a.usage)
|
|
104
|
+
.join("\n ")}\n`;
|
|
105
|
+
function unique(lists) {
|
|
106
|
+
return [...new Set(lists.flatMap((l) => l ?? []))];
|
|
107
|
+
}
|
|
108
|
+
export async function runModels(argv, io, deps) {
|
|
109
|
+
const actions = Object.values(MODELS_ACTIONS);
|
|
110
|
+
const { positionals, values, flags } = parseSubArgs(argv, unique([...actions.map((a) => a.valueOptions), COMMON_VALUE_OPTIONS]), unique(actions.map((a) => a.flagOptions)));
|
|
111
|
+
const name = positionals[0];
|
|
112
|
+
if (flags.has("help") || name === undefined) {
|
|
113
|
+
io.stdout(MODELS_USAGE);
|
|
114
|
+
return flags.has("help") ? ExitCode.Ok : ExitCode.Usage;
|
|
115
|
+
}
|
|
116
|
+
const action = Object.hasOwn(MODELS_ACTIONS, name) ? MODELS_ACTIONS[name] : undefined;
|
|
117
|
+
if (action === undefined)
|
|
118
|
+
throw new UsageError(`未知的 models 子命令:${name}`);
|
|
119
|
+
// 选项按全部动作的并集解析;这里再拒绝不属于本动作的(如 `list --json`)。
|
|
120
|
+
for (const option of [...values.keys(), ...flags]) {
|
|
121
|
+
const own = [
|
|
122
|
+
...COMMON_VALUE_OPTIONS,
|
|
123
|
+
...(action.valueOptions ?? []),
|
|
124
|
+
...(action.flagOptions ?? []),
|
|
125
|
+
];
|
|
126
|
+
if (!own.includes(option))
|
|
127
|
+
throw new UsageError(`未知选项:--${option}`);
|
|
128
|
+
}
|
|
129
|
+
const args = positionals.slice(1);
|
|
130
|
+
if (action.required !== undefined && args[0] === undefined)
|
|
131
|
+
throw new UsageError(`ama models ${name} 需要 ${action.required}`);
|
|
132
|
+
if (deps === undefined) {
|
|
133
|
+
io.stderr("ama: models 尚未装配(供应商注册表由集成批次注入)\n");
|
|
134
|
+
return ExitCode.RuntimeError;
|
|
135
|
+
}
|
|
136
|
+
const level = loadUserLevel(io, {
|
|
137
|
+
profile: values.get("profile"),
|
|
138
|
+
authFile: values.get("auth-file"),
|
|
139
|
+
});
|
|
140
|
+
for (const warning of level.warnings)
|
|
141
|
+
io.stderr(`ama: 警告:${warning}\n`);
|
|
142
|
+
const registry = await buildRegistry(level, io, deps);
|
|
143
|
+
return action.run({ io, registry, level, args, values, flags });
|
|
144
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama sessions list|show|prune`(设计 §1.2、§8)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 会话存储是 B2 的实现,经 RuntimeDeps.sessions 注入;这里只做参数、目录解析与输出格式。
|
|
5
|
+
* - list [--all]:缺省只列当前目录的会话;`--all` 列全部。
|
|
6
|
+
* - show <id>:头信息 + 条目类型统计 + 首条提示。
|
|
7
|
+
* - prune [--older-than <天>] [--dry-run]:缺省 30 天,移到 trash(不删除)。
|
|
8
|
+
*/
|
|
9
|
+
import type { CliIo, RuntimeDeps } from "../deps.js";
|
|
10
|
+
export declare const SESSIONS_USAGE = "\u7528\u6CD5\uFF1Aama sessions list [--all] [--session-dir <\u76EE\u5F55>]\n ama sessions show <id> [--session-dir <\u76EE\u5F55>]\n ama sessions prune [--older-than <\u5929>] [--dry-run] [--all] [--session-dir <\u76EE\u5F55>]\n";
|
|
11
|
+
export declare function runSessions(argv: readonly string[], io: CliIo, deps: Pick<RuntimeDeps, "sessions"> | undefined): Promise<number>;
|