@plumeai/plugin-sdk 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bin/plume-plugin.mjs +70 -0
- package/dist/client/styles/src/index.d.ts +123 -0
- package/dist/client/ui/src/avatar/AgentAvatar.d.ts +42 -0
- package/dist/client/ui/src/avatar/AvatarPile.d.ts +36 -0
- package/dist/client/ui/src/avatar/AvatarStack.d.ts +26 -0
- package/dist/client/ui/src/avatar/art.d.ts +32 -0
- package/dist/client/ui/src/fileIcon.d.ts +33 -0
- package/dist/client/ui/src/hooks/useCopied.d.ts +16 -0
- package/dist/client/ui/src/hooks/useLiveRefresh.d.ts +22 -0
- package/dist/client/ui/src/hooks/useNarrow.d.ts +16 -0
- package/dist/client/ui/src/i18n/context.d.ts +17 -0
- package/dist/client/ui/src/i18n/index.d.ts +2 -0
- package/dist/client/ui/src/i18n/list.d.ts +20 -0
- package/dist/client/ui/src/i18n/locales.d.ts +10 -0
- package/dist/client/ui/src/i18n/messages/en.d.ts +1418 -0
- package/dist/client/ui/src/i18n/messages/index.d.ts +6 -0
- package/dist/client/ui/src/i18n/messages/zh-CN.d.ts +1263 -0
- package/dist/client/ui/src/i18n/plural.d.ts +16 -0
- package/dist/client/ui/src/i18n/translate.d.ts +43 -0
- package/dist/client/ui/src/icons/index.d.ts +5 -0
- package/dist/client/ui/src/image/AnnotateCanvas.d.ts +29 -0
- package/dist/client/ui/src/image/AnnotateToolbar.d.ts +73 -0
- package/dist/client/ui/src/image/Annotator.d.ts +154 -0
- package/dist/client/ui/src/image/annotate.d.ts +227 -0
- package/dist/client/ui/src/image/paint.d.ts +35 -0
- package/dist/client/ui/src/image/screenshot-geometry.d.ts +112 -0
- package/dist/client/ui/src/image/useBarDrag.d.ts +31 -0
- package/dist/client/ui/src/image/useMovableBar.d.ts +26 -0
- package/dist/client/ui/src/image/useToolbarPlacement.d.ts +42 -0
- package/dist/client/ui/src/index.d.ts +140 -0
- package/dist/client/ui/src/inputs/ColorField.d.ts +9 -0
- package/dist/client/ui/src/inputs/ComposerToggle.d.ts +23 -0
- package/dist/client/ui/src/inputs/DateRangeField.d.ts +25 -0
- package/dist/client/ui/src/inputs/NativeField.d.ts +5 -0
- package/dist/client/ui/src/inputs/SearchField.d.ts +21 -0
- package/dist/client/ui/src/inputs/Segmented.d.ts +23 -0
- package/dist/client/ui/src/inputs/Select.d.ts +32 -0
- package/dist/client/ui/src/inputs/ShortcutRecorder.d.ts +8 -0
- package/dist/client/ui/src/inputs/Slider.d.ts +13 -0
- package/dist/client/ui/src/inputs/TextArea.d.ts +30 -0
- package/dist/client/ui/src/inputs/Toggle.d.ts +10 -0
- package/dist/client/ui/src/inputs/WorktreeToggle.d.ts +19 -0
- package/dist/client/ui/src/inputs/day-range.d.ts +57 -0
- package/dist/client/ui/src/inputs/pickers.d.ts +40 -0
- package/dist/client/ui/src/inputs/useFieldFade.d.ts +1 -0
- package/dist/client/ui/src/keyboard.d.ts +21 -0
- package/dist/client/ui/src/layout/ApprovalFrame.d.ts +33 -0
- package/dist/client/ui/src/layout/Collapse.d.ts +9 -0
- package/dist/client/ui/src/layout/CopyMark.d.ts +19 -0
- package/dist/client/ui/src/layout/Disclosure.d.ts +66 -0
- package/dist/client/ui/src/layout/ErrorBoundary.d.ts +26 -0
- package/dist/client/ui/src/layout/PageTopRow.d.ts +19 -0
- package/dist/client/ui/src/layout/PanelEmpty.d.ts +17 -0
- package/dist/client/ui/src/layout/ResizeHandle.d.ts +24 -0
- package/dist/client/ui/src/layout/RetainedViews.d.ts +32 -0
- package/dist/client/ui/src/layout/Settings.d.ts +41 -0
- package/dist/client/ui/src/layout/Splitter.d.ts +25 -0
- package/dist/client/ui/src/lib/agent-avatar.d.ts +58 -0
- package/dist/client/ui/src/lib/code/font-stack.d.ts +12 -0
- package/dist/client/ui/src/lib/code/themes.d.ts +35 -0
- package/dist/client/ui/src/lib/date-format.d.ts +8 -0
- package/dist/client/ui/src/lib/dispatches.d.ts +8 -0
- package/dist/client/ui/src/lib/hour-style.d.ts +13 -0
- package/dist/client/ui/src/lib/number-draft.d.ts +24 -0
- package/dist/client/ui/src/lib/relative-time.d.ts +18 -0
- package/dist/client/ui/src/lib/tiling.d.ts +89 -0
- package/dist/client/ui/src/lib/typing.d.ts +19 -0
- package/dist/client/ui/src/motion/RollWords.d.ts +26 -0
- package/dist/client/ui/src/motion/RollingText.d.ts +43 -0
- package/dist/client/ui/src/motion/TypedText.d.ts +9 -0
- package/dist/client/ui/src/motion/freeze.d.ts +22 -0
- package/dist/client/ui/src/motion/held.d.ts +17 -0
- package/dist/client/ui/src/motion/loaders.d.ts +65 -0
- package/dist/client/ui/src/motion/reduced.d.ts +13 -0
- package/dist/client/ui/src/motion/ticked.d.ts +24 -0
- package/dist/client/ui/src/motion/ticker.d.ts +31 -0
- package/dist/client/ui/src/motion/tokens.d.ts +29 -0
- package/dist/client/ui/src/motion/useNow.d.ts +6 -0
- package/dist/client/ui/src/overlay/Confirm.d.ts +96 -0
- package/dist/client/ui/src/overlay/ContextMenu.d.ts +40 -0
- package/dist/client/ui/src/overlay/Dialog.d.ts +60 -0
- package/dist/client/ui/src/overlay/Menu.d.ts +90 -0
- package/dist/client/ui/src/overlay/Overlay.d.ts +17 -0
- package/dist/client/ui/src/overlay/Popover.d.ts +127 -0
- package/dist/client/ui/src/overlay/Tooltip.d.ts +14 -0
- package/dist/client/ui/src/overlay/afterimage.d.ts +20 -0
- package/dist/client/ui/src/overlay/chrome-top.d.ts +8 -0
- package/dist/client/ui/src/overlay/hover-layers.d.ts +41 -0
- package/dist/client/ui/src/overlay/portal.d.ts +21 -0
- package/dist/client/ui/src/overlay/scrims.d.ts +20 -0
- package/dist/client/ui/src/overlay/tooltip-attribute.d.ts +62 -0
- package/dist/client/ui/src/primitives/Button.d.ts +64 -0
- package/dist/client/ui/src/primitives/Caret.d.ts +25 -0
- package/dist/client/ui/src/primitives/ChoiceMark.d.ts +12 -0
- package/dist/client/ui/src/primitives/ClosableTab.d.ts +23 -0
- package/dist/client/ui/src/primitives/CountUp.d.ts +21 -0
- package/dist/client/ui/src/primitives/GitHubMark.d.ts +12 -0
- package/dist/client/ui/src/primitives/IconButton.d.ts +55 -0
- package/dist/client/ui/src/primitives/MarkNumber.d.ts +12 -0
- package/dist/client/ui/src/primitives/RowDeleteButton.d.ts +6 -0
- package/dist/client/ui/src/primitives/Skeleton.d.ts +36 -0
- package/dist/client/ui/src/primitives/TabStrip.d.ts +29 -0
- package/dist/client/ui/src/primitives/Text.d.ts +341 -0
- package/dist/client/ui/src/primitives/TimeAgo.d.ts +13 -0
- package/dist/client/ui/src/primitives/useCountUp.d.ts +23 -0
- package/dist/client/ui/src/row/FlowRow.d.ts +31 -0
- package/dist/client/ui/src/row/HoverRow.d.ts +28 -0
- package/dist/client/ui/src/scroll/BackToLatest.d.ts +19 -0
- package/dist/client/ui/src/scroll/OverlayScrollbar.d.ts +9 -0
- package/dist/client/ui/src/scroll/ScrollText.d.ts +24 -0
- package/dist/client/ui/src/scroll/Scroller.d.ts +66 -0
- package/dist/client/ui/src/scroll/Sideways.d.ts +30 -0
- package/dist/client/ui/src/scroll/fade.d.ts +2 -0
- package/dist/client/ui/src/scroll/follow.d.ts +157 -0
- package/dist/client/ui/src/scroll/measure-batch.d.ts +13 -0
- package/dist/client/ui/src/scroll/memory.d.ts +22 -0
- package/dist/client/ui/src/scroll/signature.d.ts +24 -0
- package/dist/client/ui/src/scroll/useFollowBottom.d.ts +80 -0
- package/dist/client/ui/src/scroll/useSideways.d.ts +29 -0
- package/dist/contract/src/app.d.ts +16 -0
- package/dist/contract/src/changes.d.ts +50 -0
- package/dist/contract/src/diagnostics.d.ts +86 -0
- package/dist/contract/src/index.d.ts +18 -0
- package/dist/contract/src/plugin.d.ts +1391 -0
- package/dist/contract/src/resources.d.ts +79 -0
- package/dist/contract/src/service.d.ts +806 -0
- package/dist/contract/src/ui.d.ts +1039 -0
- package/dist/contract/src/usage.d.ts +102 -0
- package/dist/core/llm/ai/anthropic-messages-request.d.ts +82 -0
- package/dist/core/llm/ai/anthropic-messages.d.ts +43 -0
- package/dist/core/llm/ai/cache-routing.d.ts +53 -0
- package/dist/core/llm/ai/chat-delta.d.ts +107 -0
- package/dist/core/llm/ai/compat-key.d.ts +19 -0
- package/dist/core/llm/ai/endpoint.d.ts +57 -0
- package/dist/core/llm/ai/failure.d.ts +181 -0
- package/dist/core/llm/ai/index.d.ts +16 -0
- package/dist/core/llm/ai/openai-chat-completions-request.d.ts +49 -0
- package/dist/core/llm/ai/openai-chat-completions.d.ts +37 -0
- package/dist/core/llm/ai/openai-responses-request.d.ts +63 -0
- package/dist/core/llm/ai/openai-responses.d.ts +11 -0
- package/dist/core/llm/ai/prefix-fingerprint.d.ts +20 -0
- package/dist/core/llm/ai/reasoning-compat.d.ts +69 -0
- package/dist/core/llm/ai/request-params-compat.d.ts +39 -0
- package/dist/core/llm/ai/retry.d.ts +130 -0
- package/dist/core/llm/ai/sanitize-history.d.ts +23 -0
- package/dist/core/llm/ai/think-tag.d.ts +37 -0
- package/dist/core/llm/ai/thinking-options.d.ts +35 -0
- package/dist/core/llm/ai/tool-names.d.ts +17 -0
- package/dist/core/llm/ai/tool-pairing-compat.d.ts +53 -0
- package/dist/core/llm/ai/tool-result-images-compat.d.ts +24 -0
- package/dist/core/llm/ai/tool-schema.d.ts +20 -0
- package/dist/core/llm/ai/usage-fields.d.ts +30 -0
- package/dist/core/llm/ai/vendors/index.d.ts +20 -0
- package/dist/core/llm/ai/vendors/thinking.d.ts +39 -0
- package/dist/core/llm/ai/vendors/types.d.ts +47 -0
- package/dist/core/llm/config/image-models.d.ts +34 -0
- package/dist/core/llm/config/retry-policy.d.ts +35 -0
- package/dist/core/llm/images/chat-images.d.ts +11 -0
- package/dist/core/llm/images/dimensions.d.ts +15 -0
- package/dist/core/llm/images/generate.d.ts +15 -0
- package/dist/core/llm/images/openai-images.d.ts +11 -0
- package/dist/core/llm/images/quota.d.ts +9 -0
- package/dist/core/llm/images/shape.d.ts +44 -0
- package/dist/core/llm/images/types.d.ts +118 -0
- package/dist/core/llm/model-catalog-format.d.ts +50 -0
- package/dist/core/llm/model-catalog.d.ts +51 -0
- package/dist/core/llm/types/message.d.ts +450 -0
- package/dist/core/llm/types/provider.d.ts +169 -0
- package/dist/core/llm/types/tool.d.ts +23 -0
- package/dist/core/llm/types.d.ts +10 -0
- package/dist/core/llm/utils/pricing.d.ts +18 -0
- package/dist/core/llm/utils/record.d.ts +2 -0
- package/dist/core/llm/utils/sse.d.ts +69 -0
- package/dist/infra/rpc/src/endpoint.d.ts +36 -0
- package/dist/infra/rpc/src/index.d.ts +2 -0
- package/dist/infra/rpc/src/rpc.d.ts +54 -0
- package/dist/theme.css +336 -0
- package/dist/ui.d.ts +2 -0
- package/lib/build.mjs +143 -0
- package/lib/check.mjs +23 -0
- package/lib/dev.mjs +52 -0
- package/lib/icons.mjs +2 -0
- package/lib/init.mjs +109 -0
- package/lib/install.mjs +80 -0
- package/lib/link.mjs +44 -0
- package/lib/lint/i18n.mjs +149 -0
- package/lib/lint/imports.mjs +83 -0
- package/lib/lint/index.mjs +54 -0
- package/lib/lint/styles.mjs +160 -0
- package/lib/manifest.d.mts +9 -0
- package/lib/manifest.mjs +99 -0
- package/lib/targets.mjs +60 -0
- package/package.json +34 -4
- package/skill/SKILL.md +150 -0
- package/skill/references/components.md +2830 -0
- package/skill/references/logic.md +526 -0
- package/skill/references/style-i18n.md +70 -0
- package/skill/references/ui.md +532 -0
- package/template/AGENTS.md +17 -0
- package/template/CLAUDE.md +1 -0
- package/template/README.md +12 -0
- package/template/gitignore +2 -0
- package/template/src/index.ts +28 -0
- package/template/src/messages.ts +16 -0
- package/template/src/types.ts +8 -0
- package/template/src/ui.tsx +30 -0
- package/template/tsconfig.json +7 -0
- package/tsconfig.json +25 -0
- package/README.md +0 -3
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AssistantMessage, LlmContext, ModelConfig, Provider, ProviderConfig, RequestOptions, StreamEvent } from "../types.ts";
|
|
2
|
+
/** What provider lookup asks before falling back to the built-in adapters. */
|
|
3
|
+
export interface LlmRegistry {
|
|
4
|
+
get(api: string): Provider | undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Put a registry in front of the built-in adapters.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately a binding rather than a parameter threaded through every call site: the adapter
|
|
10
|
+
* in use is a property of the running process, and passing it down through the agent loop,
|
|
11
|
+
* compaction and every tool that streams would say otherwise. Neither host binds one; the seam is
|
|
12
|
+
* for tests and scripts, and for whatever per-session injection replaces it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useLlmRegistry(next: LlmRegistry | null): void;
|
|
15
|
+
/** Stream one assistant turn. Yields incremental events and returns the final message. */
|
|
16
|
+
export declare function streamAssistant(providerConfig: ProviderConfig, model: ModelConfig, context: LlmContext, options?: RequestOptions): AsyncGenerator<StreamEvent, AssistantMessage>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Our messages, in the shape the OpenAI Chat Completions API wants (`POST /v1/chat/completions`).
|
|
3
|
+
*/
|
|
4
|
+
import type { Message, ToolCallContent, ToolSpec } from "../types.ts";
|
|
5
|
+
import type { ReasoningReplay } from "./reasoning-compat.ts";
|
|
6
|
+
/** 编码时需要知道的模型能力。只取用得到的那一项,免得一个纯翻译函数被整个 `ModelConfig` 绑住。 */
|
|
7
|
+
export interface ChatCompletionsModelCapabilities {
|
|
8
|
+
supportsImages: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function toChatCompletionsTools(tools: ToolSpec[]): unknown[];
|
|
11
|
+
/**
|
|
12
|
+
* Cleans conversation history for the OpenAI Chat Completions wire protocol.
|
|
13
|
+
*
|
|
14
|
+
* Chat Completions enforces strict invariants:
|
|
15
|
+
* 1. An assistant message must have either non-empty content or tool_calls. Empty content ("")
|
|
16
|
+
* without tool_calls causes HTTP 400 (e.g. Gemini/relays returning INVALID_ARGUMENT).
|
|
17
|
+
* When a model spends its turn thinking and emits no text/tools, that turn is a no-op;
|
|
18
|
+
* we prune it along with any synthetic nudge following it.
|
|
19
|
+
* 2. Tool results must correspond to an assistant tool call. Orphan tool results that have no
|
|
20
|
+
* prior assistant call are dropped or sanitized to avoid 400 errors.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sanitizeChatCompletionsHistory(messages: Message[]): Message[];
|
|
23
|
+
export declare function toChatCompletionsMessages(systemPrompt: string, messages: Message[], reasoning?: ReasoningReplay, model?: ChatCompletionsModelCapabilities,
|
|
24
|
+
/** 缺推理的助手轮补一个空的 `reasoning_content`。撞上过才开,见 `padsMissingReasoning`。 */
|
|
25
|
+
padMissingReasoning?: boolean): unknown[];
|
|
26
|
+
/** 这个模型值得放缓存断点:Anthropic 的模型,经一个把请求翻译成 Anthropic 的中转(OpenRouter、LiteLLM 一类)。 */
|
|
27
|
+
export declare function wantsCacheBreakpoints(modelId: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 在 system 和最后两条消息上放 `cache_control` 断点。
|
|
30
|
+
*
|
|
31
|
+
* Anthropic 的缓存只认显式断点,而这条链原来一个都不放:Claude 经 Chat Completions 中转时每一轮都把
|
|
32
|
+
* 整段历史按全价重读。原生那条链早就在尾部滚动断点(`anthropic-messages-request.ts` 的
|
|
33
|
+
* `applyPromptCaching`)。这里放 system 和最后两条;断点挂在**内容块**上而不是消息上,所以
|
|
34
|
+
* 字符串内容要改写成一个 text 块——`prefix-fingerprint.ts` 把这样的单块认作原来那个字符串。
|
|
35
|
+
* Anthropic 一次最多四个断点,这里用三个。
|
|
36
|
+
*
|
|
37
|
+
* 只挂在有字的 text 块上:空 text 块不能带断点,只有 `tool_calls` 的助手消息(`content: ""`)跳过、往前找。
|
|
38
|
+
* 端点不认这个字段时会点名它,学到 `cache-control` 就不再放(`request-params-compat.ts`)。
|
|
39
|
+
*/
|
|
40
|
+
export declare function withCacheBreakpoints(messages: unknown[]): unknown[];
|
|
41
|
+
/**
|
|
42
|
+
* 回放一次工具调用时发的参数串。
|
|
43
|
+
*
|
|
44
|
+
* 优先发流里收到的原文:那是上一次请求里服务商亲眼见过的字节,换成重新序列化的版本(键序、空格、
|
|
45
|
+
* 转义都可能不同)会让这一段缓存前缀失效。原文不是合法 JSON 时(截断、模型吐坏了)才改发解析后的
|
|
46
|
+
* 参数:原样透传在官方端点上没事,但把请求再翻译成 Anthropic / Gemini 的中转要先解析它,解析不了
|
|
47
|
+
* 就是 400,而这条调用进了历史,之后每一轮都是这个 400。
|
|
48
|
+
*/
|
|
49
|
+
export declare function replayArguments(call: ToolCallContent): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Chat Completions API adapter (`POST /v1/chat/completions`).
|
|
3
|
+
*/
|
|
4
|
+
import type { Provider } from "../types.ts";
|
|
5
|
+
import { type SentParams } from "./request-params-compat.ts";
|
|
6
|
+
export declare const openaiChatCompletionsProvider: Provider;
|
|
7
|
+
/**
|
|
8
|
+
* 输出上限该写在哪个键上——这条只能靠撞出来。
|
|
9
|
+
*
|
|
10
|
+
* 和 `reasoning-compat.ts`、`request-params-compat.ts` 同一个路子,理由也同一个:**我们手上没有普查
|
|
11
|
+
* 表**。按厂商、主机名、模型家族把这件事查出来,要的是几百个端点的实测积累,攒不起来也维护不起。
|
|
12
|
+
*
|
|
13
|
+
* 能靠的是另一样东西:**这种 400 的错误串自带诊断**——它直接点名该用哪个字段。所以默认保持这条链现在
|
|
14
|
+
* 发的那个键(不动任何现在能用的端点),撞上那句话就学到结论、换个键重发一次。
|
|
15
|
+
*
|
|
16
|
+
* 采样参数和 `tool_choice` 那两条轴**不在这里**,在 `request-params-compat.ts`——那是三条链共用的一份,
|
|
17
|
+
* 两条链各写一份同样的正则是纯负债。这里只留「换字段名」这一条,因为它不是「撤掉一个参数」,撤不掉:
|
|
18
|
+
* 输出上限必须发,只是键名有两种。
|
|
19
|
+
*
|
|
20
|
+
* 记在内存里,不落盘,理由见 `reasoning-compat.ts` 顶部:进程重启后重新学一次的代价是一次自愈的重发,
|
|
21
|
+
* 换来的是不必为它做设置迁移,也不必担心一条学错的结论永久粘在用户的配置里。
|
|
22
|
+
*/
|
|
23
|
+
export type MaxTokensField = "max_tokens" | "max_completion_tokens";
|
|
24
|
+
/** 这个模型该用哪个键。没撞过之前就是这条链一直在发的那个。 */
|
|
25
|
+
export declare function maxTokensField(providerId: string, modelId: string): MaxTokensField;
|
|
26
|
+
/** 测试用:把学到的都忘掉。 */
|
|
27
|
+
export declare function resetChatCompletionsCompat(): void;
|
|
28
|
+
/**
|
|
29
|
+
* 从一次失败里学点东西。返回「结论变了,值得换个形状重发一次」。
|
|
30
|
+
*
|
|
31
|
+
* 签名对上 `withReasoningRetry` 的 `alsoLearn`,而且**排在推理那条梯子前面**看——理由见那个参数的注释:
|
|
32
|
+
* 指向明确的轴先看,误判的机会小得多。这里两条的指向再明确不过,错误串里点着字段名。
|
|
33
|
+
*
|
|
34
|
+
* 两个学习器都要问,**不能用 `||` 短路**:一句话可以同时点名两样(`max_tokens` 该换名、`temperature`
|
|
35
|
+
* 不该发),短路会漏掉后一条,然后下一次重发再撞一遍同一句话。
|
|
36
|
+
*/
|
|
37
|
+
export declare function learnChatCompletionsCompat(providerId: string, modelId: string, error: string, sent?: SentParams): boolean;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Our messages, in the shape the Responses API wants.
|
|
3
|
+
*
|
|
4
|
+
* Purely a translation: no network, no state, no decisions beyond how each kind of content maps.
|
|
5
|
+
* Separated from the streaming half because the two are read for different reasons — this one when
|
|
6
|
+
* a message is not being sent correctly, the other when a reply is not being read correctly.
|
|
7
|
+
*/
|
|
8
|
+
import type { Message, ToolSpec } from "../types.ts";
|
|
9
|
+
import type { ReasoningReplay } from "./reasoning-compat.ts";
|
|
10
|
+
import type { ToolPairing } from "./tool-pairing-compat.ts";
|
|
11
|
+
import type { ToolResultImages } from "./tool-result-images-compat.ts";
|
|
12
|
+
/**
|
|
13
|
+
* Who this request is going to, so a handle from someone else can be told apart from our own.
|
|
14
|
+
*
|
|
15
|
+
* Every assistant message records the provider and model that produced it. A reasoning item id is
|
|
16
|
+
* that provider's private handle on its own chain of thought — it means nothing to anyone else, and
|
|
17
|
+
* is rejected rather than ignored when replayed to them.
|
|
18
|
+
*/
|
|
19
|
+
export interface ResponsesHome {
|
|
20
|
+
provider: string;
|
|
21
|
+
model: string;
|
|
22
|
+
/**
|
|
23
|
+
* 这个模型收不收图片。省略按「收」算。
|
|
24
|
+
*
|
|
25
|
+
* 省略等于收,是因为老会话和大部分调用点并不知道这件事,而把「不知道」当成「不收」会把图片从本来好好
|
|
26
|
+
* 的请求里抹掉。反过来错的代价小:多发一张图给不收图的端点会被拒,而那正是下面要防的;漏判只是维持
|
|
27
|
+
* 现状。
|
|
28
|
+
*/
|
|
29
|
+
supportsImages?: boolean;
|
|
30
|
+
/** Where a tool result's image goes; see `tool-result-images-compat.ts`. Omitted means inline. */
|
|
31
|
+
toolImages?: ToolResultImages;
|
|
32
|
+
/**
|
|
33
|
+
* 这个端点要不要每个助手轮都以推理项开头(没有就补一个合成的,见 `toResponsesInput` 的补位分支)。
|
|
34
|
+
* 省略按「要」算。
|
|
35
|
+
*
|
|
36
|
+
* 省略等于要,理由同 `supportsImages`:不知道的调用点维持原来的形状。适配器知道发往哪里,由它说
|
|
37
|
+
* 「不要」——补出来的那一项在官方 OpenAI 上是一次必然的 400(`maximum length 0`)。
|
|
38
|
+
*/
|
|
39
|
+
leadingReasoning?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 一轮里的多个工具调用怎么排:成组(所有调用,然后所有结果)还是交错(一问一答)。
|
|
43
|
+
*
|
|
44
|
+
* 两家要求相反,见 `tool-pairing-compat.ts`。这里只负责按给定的那一档编码,选哪一档是那边的事。
|
|
45
|
+
*
|
|
46
|
+
* 这段原本写死交错,理由是把 Responses 翻译成 Chat Completions 的中转需要它——那个理由是真的,错的是
|
|
47
|
+
* 把它当成了**普适**的形状:
|
|
48
|
+
*
|
|
49
|
+
* an assistant message with 'tool_calls' must be followed by tool messages responding to
|
|
50
|
+
* each 'tool_call_id'. The following tool_call_ids did not have response messages: bash:0
|
|
51
|
+
*
|
|
52
|
+
* 代价是 2026-09-11 的两个报废会话。`api.deepseek.com` 的 Responses 要成组,交错排一律 400,而它报的是
|
|
53
|
+
* `The reasoning_text in the thinking mode must be passed back to the API.`——一句跟工具无关的话。真实端点
|
|
54
|
+
* 上二分过:一对调用/结果 200,第二对一加就 400;改成成组之后,推理项发不发、带不带文本,全都 200。
|
|
55
|
+
*
|
|
56
|
+
* 不管哪一档,这里都保证结果按**调用顺序**排,而不是按完成顺序。结果是各个工具跑完的时候记下来的,从日志
|
|
57
|
+
* 重建的历史里它们按完成先后躺着;在这里配对意味着发出去的形状不取决于哪个工具更快。
|
|
58
|
+
*
|
|
59
|
+
* 一条结果如果在它前面的助手消息里找不到对应的调用——日志被截断、有人编辑掉了那次调用——保留在原位,不
|
|
60
|
+
* 丢弃:它是历史,而凭空造一个调用去挂住它比原样传过去更糟。
|
|
61
|
+
*/
|
|
62
|
+
export declare function toResponsesInput(messages: Message[], home?: ResponsesHome, reasoning?: ReasoningReplay, pairing?: ToolPairing): unknown[];
|
|
63
|
+
export declare function toResponsesTools(tools: ToolSpec[]): unknown[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Responses API adapter (`POST /v1/responses`).
|
|
3
|
+
*
|
|
4
|
+
* The Responses format is item-based rather than message-based: assistant text, reasoning
|
|
5
|
+
* and function calls are sibling items in one flat `input` array. Reasoning items carry an
|
|
6
|
+
* opaque `encrypted_content` that must be replayed verbatim for the model to keep its chain
|
|
7
|
+
* of thought across tool calls, which is why `include: ["reasoning.encrypted_content"]` is
|
|
8
|
+
* always requested.
|
|
9
|
+
*/
|
|
10
|
+
import type { Provider } from "../types.ts";
|
|
11
|
+
export declare const openaiResponsesProvider: Provider;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求前缀在哪里变了:缓存诊断的归因证据。
|
|
3
|
+
*
|
|
4
|
+
* 诊断只从 usage 看得出「本该读到的没读到」,看不出是本地改了前缀(提示词、工具、剪枝、协议编码),
|
|
5
|
+
* 还是服务商那边清掉了缓存。这里把最终请求体按缓存前缀的顺序切段——工具定义、系统提示词、请求参数、
|
|
6
|
+
* 逐条消息——各算一个哈希,和同一会话的上一次请求比出第一处不同。只留位置和长度,不留正文。
|
|
7
|
+
*
|
|
8
|
+
* 三条协议的请求体都按这个形状切:Anthropic `tools`/`system`/`messages`,Responses
|
|
9
|
+
* `tools`/`instructions`/`input`,Chat Completions `tools`/`messages`(系统提示词是开头那条 `system` 消息,同样记成 `system`)。
|
|
10
|
+
* 协议放的 `cache_control` 断点每次跟着最后一条消息挪,不算内容,哈希前去掉(只去协议放的那几处)。
|
|
11
|
+
*/
|
|
12
|
+
import type { RequestPrefix } from "../types.ts";
|
|
13
|
+
export interface PrefixSegment {
|
|
14
|
+
name: string;
|
|
15
|
+
hash: string;
|
|
16
|
+
chars: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function payloadSegments(body: unknown): PrefixSegment[];
|
|
19
|
+
/** `after` 和 `before` 比:`before` 原样是 `after` 的前缀时没有 `change`。 */
|
|
20
|
+
export declare function comparePrefix(before: readonly PrefixSegment[], after: readonly PrefixSegment[]): RequestPrefix;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一个端点对「把模型自己的推理还回去」这件事的态度,问出来之后记住。
|
|
3
|
+
*
|
|
4
|
+
* 这里不是可有可无的优化,是因为**各家的要求互相矛盾,一个全局形状在数学上满足不了**。两条都是真实
|
|
5
|
+
* 端点上量出来的:
|
|
6
|
+
*
|
|
7
|
+
* - 某中转上的 Claude:推理项可以有,但**必须带得动签名**。换过模型之后签名被
|
|
8
|
+
* `stripStaleHandles` 剥掉了,剩下一个没有句柄的思考块,于是
|
|
9
|
+
* `messages.1.content.0.thinking.signature: Field required`——补上 summary 也只是换成
|
|
10
|
+
* `thinking.thinking: Field required`,因为缺的那样东西我们根本没有。
|
|
11
|
+
* - 某中转上的 `gpt-oss-120b-medium`:input 里**只要有** reasoning 项就 400,四种形状全试过。
|
|
12
|
+
*
|
|
13
|
+
* 所以是三档,不是开关。三档之间是一条梯子,越往下发得越少:
|
|
14
|
+
*
|
|
15
|
+
* replay(都发) → handled(只发带句柄的) → omit(一个都不发)
|
|
16
|
+
*
|
|
17
|
+
* 被顶回来一次就往下走一格,重发。往下走是安全的方向——少发一点推理最多让模型接不回自己那条思维链,
|
|
18
|
+
* 而多发一格是整个请求被拒。第二种信号(「你得还回来」)把它一次拉回顶格。
|
|
19
|
+
*
|
|
20
|
+
* 只认有实测证据的错误串。多认一条的代价是把别的原因造成的 400 误判成形状问题,然后拿一个改坏了的
|
|
21
|
+
* 请求去重发——那比不认还糟。
|
|
22
|
+
*
|
|
23
|
+
* 记在内存里,不落盘。进程重启后重新学一次,代价是一次自愈的重发;换来的是不必为它做一次设置迁移,
|
|
24
|
+
* 也不必担心一条学错的结论永久粘在用户的配置里。
|
|
25
|
+
*/
|
|
26
|
+
/** 推理块在下一轮请求里的去向。 */
|
|
27
|
+
export type ReasoningReplay = "replay" | "handled" | "omit";
|
|
28
|
+
/** 这个模型该发哪一档。默认顶格——那是绝大多数端点要的,也是没撞过之前唯一有依据的猜测。 */
|
|
29
|
+
export declare function reasoningReplay(providerId: string, modelId: string): ReasoningReplay;
|
|
30
|
+
/**
|
|
31
|
+
* 从一次失败里学点东西。返回「结论变了,值得换个形状重发一次」。
|
|
32
|
+
*
|
|
33
|
+
* 已经在梯子最底下还被顶回来,返回 false——再往下没有格子了,重发只是多烧一次钱。
|
|
34
|
+
*/
|
|
35
|
+
export declare function learnReasoningReplay(providerId: string, modelId: string, error: string): boolean;
|
|
36
|
+
/** 这个模型的历史助手轮缺推理时,要不要补一个空的 `reasoning_content`。 */
|
|
37
|
+
export declare function padsMissingReasoning(providerId: string, modelId: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* 撞上「必须带回来」时,先学会补空串,再轮到 `learnReasoningReplay` 那次原样重试。
|
|
40
|
+
*
|
|
41
|
+
* 只有 Chat Completions 链把它挂进 `withReasoningRetry` 的 `alsoLearn`:Responses 链不读这个结论,在那里学到
|
|
42
|
+
* 等于白白多发一次一模一样的请求。只在梯子顶格时学——不在顶格说明还有推理没发,先让梯子把它拉回来。
|
|
43
|
+
*/
|
|
44
|
+
export declare function learnReasoningPadding(providerId: string, modelId: string, error: string): boolean;
|
|
45
|
+
/** 测试用:把学到的都忘掉。 */
|
|
46
|
+
export declare function resetReasoningCompat(): void;
|
|
47
|
+
/**
|
|
48
|
+
* 跑一次请求;被端点用「推理形状不对」顶回来时,换成它要的那一档,重发。
|
|
49
|
+
*
|
|
50
|
+
* 套在 `retryStream` **外面**,因为那一层按定义不会重试一个 400——同一个请求再发一遍还是同一个 400,
|
|
51
|
+
* 它拒绝得对。这里重发的是一个**不同的**请求,所以是另一件事。
|
|
52
|
+
*
|
|
53
|
+
* 两条闸:
|
|
54
|
+
*
|
|
55
|
+
* - 最多走完整条梯子,外加一步留给 `alsoLearn` 那个轴(它自己也只学一次)。每个学习器到底了都返回
|
|
56
|
+
* false,循环自己停。任何一个轴加格子,这里的上界要跟着改——它是两个轴的步数之和,不是某一个的。
|
|
57
|
+
* - 已经吐过字(`costIncurred`)就不重来。那些 token 服务商已经收过钱了,而且界面上已经画出了
|
|
58
|
+
* 半个回答,重发会让它凭空再来一遍。请求形状的 400 发生在生成之前,这一条正常不会挡住它。
|
|
59
|
+
*/
|
|
60
|
+
export declare function withReasoningRetry<T>(providerId: string, modelId: string, reset: () => void,
|
|
61
|
+
/**
|
|
62
|
+
* 别的轴也来看一眼这次失败——返回 true 表示它学到了东西,值得换个形状重发。
|
|
63
|
+
*
|
|
64
|
+
* 先问它再问推理那条梯子,因为**这两个轴会抢同一句话**。DeepSeek 的 Responses 在工具排列不对时报的
|
|
65
|
+
* 是 `The reasoning_text in the thinking mode must be passed back to the API.`,一句跟工具毫无关系
|
|
66
|
+
* 的话,而它正好落进下面 `REQUIRES` 的正则里。让推理那条先看,它会把这次失败认成自己的、退回一个
|
|
67
|
+
* 于事无补的结论;让指向明确的那条先看,误判的机会小得多。
|
|
68
|
+
*/
|
|
69
|
+
alsoLearn: ((providerId: string, modelId: string, error: string) => boolean) | undefined, run: (replay: ReasoningReplay) => AsyncGenerator<T, void>): AsyncGenerator<T, void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 请求体里那些「这个端点不吃这个字段」的参数,撞出来之后记住。
|
|
3
|
+
*
|
|
4
|
+
* 和另外两个轴(`reasoning-compat.ts` 管推理怎么还、`tool-pairing-compat.ts` 管工具怎么排)是并列的
|
|
5
|
+
* 第三件事,各学各的。压进同一条梯子的代价上一轮付过了:端点说 A、梯子退 B,对不上号时只能靠碰巧。
|
|
6
|
+
*
|
|
7
|
+
* 这里管的七个参数有一个共同点——**它们都不是必需品**。少发一个最多损失一点控制力(思考没关干净、
|
|
8
|
+
* 采样参数没生效),而多发一个是整个请求被拒。所以默认全发、撞了就撤,方向永远是安全的那边。
|
|
9
|
+
*
|
|
10
|
+
* 为什么是「学」而不是「配」:这四条在作者能测到的两个端点上**全部 200**(2026-09-11,
|
|
11
|
+
* `test/responses-params-probe.ts`)。也就是说我们手上
|
|
12
|
+
* 没有能复现它们的端点,没有资格替所有人改默认值。我们也没有一张人工普查出来的表能直接查——但错误串
|
|
13
|
+
* 自带诊断,撞一次就够了。
|
|
14
|
+
*
|
|
15
|
+
* 已知的先验不靠学,写在 `vendors/`:Gemini 系、OpenAI 的 o 系列和旧 gpt-5 拒 `effort: "none"`。这里的
|
|
16
|
+
* 机制是给**没见过的端点**兜底的。
|
|
17
|
+
*
|
|
18
|
+
* 记在内存里,不落盘,理由同另外两个轴。
|
|
19
|
+
*/
|
|
20
|
+
/** 一个可以在撞墙后撤掉的请求参数。 */
|
|
21
|
+
export type DroppedParam = "reasoning-off" | "reasoning-summary" | "tool-choice" | "sampling" | "include-encrypted" | "cache-key" | "cache-control";
|
|
22
|
+
/** 这个模型上该省掉哪些参数。没撞过就是一个都不省。 */
|
|
23
|
+
export declare function droppedParams(providerId: string, modelId: string): ReadonlySet<DroppedParam>;
|
|
24
|
+
/** 这次被拒的请求里实际发了什么。给了就用来排除「撤掉它也不会改变请求」的信号。 */
|
|
25
|
+
export interface SentParams {
|
|
26
|
+
/** 发了「关推理」那个值(`effort: "none"` / `reasoning_effort: "none"`)没有。 */
|
|
27
|
+
reasoningOff?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 从一次失败里学点东西。返回「结论变了,值得削掉这个参数重发一次」。
|
|
31
|
+
*
|
|
32
|
+
* 已经撤过同一个参数还被同一句话顶回来,返回 false——再发一次还是同样的结果,只是多烧一次钱。
|
|
33
|
+
*
|
|
34
|
+
* `sent` 不给时按从前的行为只看错误串;适配器应当给——请求里根本没有 `none` 时,一句提到 effort 的
|
|
35
|
+
* 400 说的一定是别的档位,撤「关推理」既改不了这次的请求,还会让这个模型以后关不掉思考。
|
|
36
|
+
*/
|
|
37
|
+
export declare function learnDroppedParam(providerId: string, modelId: string, error: string, sent?: SentParams): boolean;
|
|
38
|
+
/** 测试用:把学到的都忘掉。 */
|
|
39
|
+
export declare function resetRequestParamsCompat(): void;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** Shared request/stream budget prevents nested retry loops from multiplying the configured limit. */
|
|
2
|
+
import { type RetryPolicy, type RetryPolicySource, type RetryFailure } from "../config/retry-policy.ts";
|
|
3
|
+
import { type Failure } from "./failure.ts";
|
|
4
|
+
export declare class RetryBudget {
|
|
5
|
+
private used;
|
|
6
|
+
/**
|
|
7
|
+
* 自带上限的失败(`Failure.retryLimit`)各自重试过几次,按指纹记。
|
|
8
|
+
*
|
|
9
|
+
* 不能和 `used[kind]` 共用:那是同一类所有失败的总账。先连着 503 重试四次,轮到空回答时总账已经是
|
|
10
|
+
* 4,它自己一次没试就被判「用满了」。它的上限说的是「这一种重发了几次」,账得单独记。
|
|
11
|
+
*/
|
|
12
|
+
private limited;
|
|
13
|
+
private readonly read;
|
|
14
|
+
private readonly attempts?;
|
|
15
|
+
private cache?;
|
|
16
|
+
constructor(policy?: RetryPolicySource, attempts?: number);
|
|
17
|
+
/**
|
|
18
|
+
* The rules in force right now, not the ones this request started under.
|
|
19
|
+
*
|
|
20
|
+
* Normalising is not free and this is read on every attempt, so the result is kept until the
|
|
21
|
+
* settings object itself is replaced — which is what saving the settings page does.
|
|
22
|
+
*/
|
|
23
|
+
get policy(): RetryPolicy;
|
|
24
|
+
/**
|
|
25
|
+
* 还能不能再试一次。
|
|
26
|
+
*
|
|
27
|
+
* 失败可以选择传进来,因为有那么几种失败自己带着上限(见 `Failure.retryLimit`),而它比用户
|
|
28
|
+
* 的策略更有发言权的情形只有一种:重发不可能改变结果。两个数取更严的那个——用户设了 2 次就
|
|
29
|
+
* 是 2 次,这里不会把它放宽成 4。
|
|
30
|
+
*
|
|
31
|
+
* 不传也是对的:调用方手里没有分类结论时(还没发出去的那一次),问的就是纯粹的策略。
|
|
32
|
+
*/
|
|
33
|
+
available(kind: RetryFailure, failure?: Failure): boolean;
|
|
34
|
+
/** 传入 `failure` 时,自带上限的那种失败另记一笔,见 `limited`。 */
|
|
35
|
+
next(kind: RetryFailure, failure?: Failure): {
|
|
36
|
+
attempt: number;
|
|
37
|
+
delayMs: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export interface RetryOptions {
|
|
41
|
+
budget?: RetryBudget;
|
|
42
|
+
/** Total attempts, including the first. */
|
|
43
|
+
attempts?: number;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
/** Called before each wait, so a caller can tell the user what is happening. */
|
|
46
|
+
onRetry?: (info: {
|
|
47
|
+
attempt: number;
|
|
48
|
+
delayMs: number;
|
|
49
|
+
reason: string;
|
|
50
|
+
failure?: Failure;
|
|
51
|
+
}) => void;
|
|
52
|
+
/** Injected in tests so they do not sleep. */
|
|
53
|
+
sleep?: (ms: number) => Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* 这个请求要的是 SSE。200 而正文明摆着不是(见 `streamOrFailure`)的,当失败判,不交给流解析。
|
|
56
|
+
*
|
|
57
|
+
* 要调用方开口说,因为不是每个经过这里的请求都要流:一次性的 JSON 请求,200 加 JSON 就是成功。
|
|
58
|
+
*/
|
|
59
|
+
stream?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 值得再试一次吗——问的是 `failure.ts`,不是这里。
|
|
63
|
+
*
|
|
64
|
+
* 这两个函数从前各自守着一份白名单,是四份互不知情的判断里的两份。留下这层薄壳是因为调用点还在
|
|
65
|
+
* 用它们的名字,而壳里已经换成了同一个分类器:认不出来的东西现在会被重试,而不是被拒之门外。
|
|
66
|
+
*/
|
|
67
|
+
export declare function isRetryableError(error: unknown): boolean;
|
|
68
|
+
export declare function isRetryableStatus(status: number, body?: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* How long to wait, honouring the server's own answer when it gives one.
|
|
71
|
+
*
|
|
72
|
+
* A server under load knows better than any curve we could pick, so its number wins outright —
|
|
73
|
+
* only capped, never shortened. Ours is the fallback, and it starts where a person would expect a
|
|
74
|
+
* retry to start rather than where a tight loop would: the first version began at 600ms and
|
|
75
|
+
* tripled, which spent three attempts inside two and a half seconds and read as the app hammering
|
|
76
|
+
* a server that had just said it was busy.
|
|
77
|
+
*/
|
|
78
|
+
export declare function retryDelay(attempt: number, response?: Response, body?: string): number;
|
|
79
|
+
/**
|
|
80
|
+
* Perform a request, retrying everything except what is certain not to change.
|
|
81
|
+
*
|
|
82
|
+
* 成功的响应原样返回。其余一律抛 `FailureError`——包括 4xx:从前它们是被「原样返回,由调用方
|
|
83
|
+
* 自己去报」的,而三个调用方拿到之后做的第一件事都是 `if (!response.ok) throw`,于是那个结论在
|
|
84
|
+
* 路上被重新猜了一遍,猜出来的东西还不如原来准(一个 `HTTP 401: ...` 字符串在流那一层看起来像
|
|
85
|
+
* 「没见过的连接问题」)。判断带着结论一起走。
|
|
86
|
+
*/
|
|
87
|
+
export declare function fetchWithRetry(doFetch: typeof globalThis.fetch, url: string, init: RequestInit, options?: RetryOptions): Promise<Response>;
|
|
88
|
+
/**
|
|
89
|
+
* A tool call's id, invented if the provider did not supply one.
|
|
90
|
+
*
|
|
91
|
+
* The id is the only thing tying a call to its result and to the card on screen. A provider
|
|
92
|
+
* that omits it — some relays drop `call_id` on a truncated stream — used to yield the empty
|
|
93
|
+
* string for every such call, so they all collided on one entry: the newest call reset the
|
|
94
|
+
* shared record to "running" and every earlier card in the transcript started spinning again,
|
|
95
|
+
* all showing the same elapsed time because they were all reading the same object.
|
|
96
|
+
*
|
|
97
|
+
* The fallback is remembered per output index, because one call arrives across several events
|
|
98
|
+
* and they have to agree on what it is called.
|
|
99
|
+
*/
|
|
100
|
+
export declare function toolCallId(given: unknown, outputIndex: number, invented: Map<number, string>): string;
|
|
101
|
+
/**
|
|
102
|
+
* Run a streamed request, and start it over if the stream itself dies.
|
|
103
|
+
*
|
|
104
|
+
* `fetchWithRetry` covers getting the connection; this covers keeping it. They are different
|
|
105
|
+
* failures with the same cause and very different odds: a request that takes forty seconds to
|
|
106
|
+
* stream a large reply is exposed to a dropped socket for the whole of it, and a long piece of
|
|
107
|
+
* work is exactly where the replies are longest. Losing one there ends the turn — and with it a
|
|
108
|
+
* plan the agent was eight steps into.
|
|
109
|
+
*
|
|
110
|
+
* Starting over is safe because nothing has happened yet. Tools are executed by the caller after
|
|
111
|
+
* a complete reply arrives, so a half-streamed one has changed nothing; the only cost is the
|
|
112
|
+
* tokens spent saying it again.
|
|
113
|
+
*
|
|
114
|
+
* `reset` is called before every attempt to clear whatever the last one accumulated. Anything
|
|
115
|
+
* already emitted to the UI is replaced by what the retry emits, because each update carries the
|
|
116
|
+
* whole message rather than a delta to apply.
|
|
117
|
+
*/
|
|
118
|
+
export declare function retryStream<T>(attempt: (attemptNumber: number) => AsyncGenerator<T, void>, options: {
|
|
119
|
+
budget?: RetryBudget;
|
|
120
|
+
attempts?: number;
|
|
121
|
+
signal?: AbortSignal;
|
|
122
|
+
reset: () => void;
|
|
123
|
+
onRetry?: (info: {
|
|
124
|
+
attempt: number;
|
|
125
|
+
delayMs: number;
|
|
126
|
+
reason: string;
|
|
127
|
+
failure?: Failure;
|
|
128
|
+
}) => void;
|
|
129
|
+
sleep?: (ms: number) => Promise<void>;
|
|
130
|
+
}): AsyncGenerator<T, void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enforces protocol-level invariants on conversation history before sending to providers.
|
|
3
|
+
*
|
|
4
|
+
* All major LLM providers enforce strict tool pairing rules on the wire:
|
|
5
|
+
* 1. OpenAI Responses:
|
|
6
|
+
* Every `function_call` must be accompanied by its `function_call_output`.
|
|
7
|
+
* An unanswered tool call causes HTTP 400 (`No tool output found for function call ...`).
|
|
8
|
+
* 2. Anthropic Messages:
|
|
9
|
+
* Every `tool_use` block in an assistant message must have a matching `tool_result`
|
|
10
|
+
* in the immediately following user message. Missing results cause HTTP 400.
|
|
11
|
+
* 3. OpenAI Chat Completions:
|
|
12
|
+
* An assistant message with `tool_calls` must be followed by `role: "tool"` messages
|
|
13
|
+
* responding to each `tool_call_id`. Missing results cause HTTP 400.
|
|
14
|
+
*
|
|
15
|
+
* If a session's history contains an assistant message with tool calls that were never
|
|
16
|
+
* answered (e.g. aborted mid-turn, process crash before execution, or network disconnect),
|
|
17
|
+
* sending it raw crashes the conversation irrevocably.
|
|
18
|
+
*
|
|
19
|
+
* This sanitizer synthesizes a clear error toolResult for any unanswered toolCall,
|
|
20
|
+
* preserving turn atomicity and repairing protocol invariants without losing history.
|
|
21
|
+
*/
|
|
22
|
+
import type { Message } from "../types.ts";
|
|
23
|
+
export declare function sanitizeToolPairing(messages: Message[]): Message[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 写在 content 里的 `<think>…</think>`,流式切成思考。
|
|
3
|
+
*
|
|
4
|
+
* 单独一个文件,因为它是这条链上唯一一段**跨 chunk 带状态**的文本切分;其余的入站判断都是一帧看一帧的
|
|
5
|
+
* 纯函数,在 `chat-delta.ts`。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 内联 `<think>` 切出来的思考块记在 `reasoningField` 上的标记。
|
|
9
|
+
*
|
|
10
|
+
* 它不是一个字段名:回放时编码那边见到它,就把思考按原样包回 `<think>…</think>` 放在正文前面(见
|
|
11
|
+
* `openai-chat-completions-request.ts`)。MiniMax M2 的文档要求多轮里把带 `<think>` 的整段回答原样带回,
|
|
12
|
+
* 拆成 `reasoning_content` 发回去它不认。
|
|
13
|
+
*/
|
|
14
|
+
export declare const THINK_TAG_FIELD = "<think>";
|
|
15
|
+
/** 切出来的一段:思考还是正文。 */
|
|
16
|
+
interface ThinkSegment {
|
|
17
|
+
kind: "thinking" | "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 把正文开头的 `<think>…</think>` 切成思考。
|
|
22
|
+
*
|
|
23
|
+
* 没开 reasoning parser 的 Ollama / vLLM 上的 Qwen3、R1,和默认配置的 MiniMax M2,都把推理写在 content 里。
|
|
24
|
+
* 从前它们原样进正文:用户看见一大段思考,只有思考没有结论的一轮也不会被当成「没有回答」。
|
|
25
|
+
*
|
|
26
|
+
* 只认**正文最开头**(允许前导空白)的那个 `<think>`。正文中间出现的是模型在讨论这个标签,切了就是吞掉
|
|
27
|
+
* 回答。标签会被 SSE 切开(`<thi` / `nk>`),所以开头那几个字和结尾那几个字要扣着等下一个 chunk。
|
|
28
|
+
*/
|
|
29
|
+
export declare class ThinkTagSplitter {
|
|
30
|
+
/** 还没看到正文的第一个字 → 在 `<think>` 里 → 刚出 `</think>`、吃掉它后面的空行 → 正文。 */
|
|
31
|
+
private state;
|
|
32
|
+
private held;
|
|
33
|
+
push(chunk: string): ThinkSegment[];
|
|
34
|
+
/** 流结束:扣着的那一截按它所在的位置交出去。没闭合的 `<think>` 里的全是思考。 */
|
|
35
|
+
end(): ThinkSegment[];
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ModelConfig, ThinkingLevel, ThinkingOption } from "../types/provider.ts";
|
|
2
|
+
/** 标准档位,从浅到深。选中的档位模型没有时,按这个顺序就近找。 */
|
|
3
|
+
export declare const THINKING_LEVELS: readonly ThinkingLevel[];
|
|
4
|
+
/**
|
|
5
|
+
* 一组标准档位的选项。默认档是 `preferredDefault`(没给就是 medium)在这组里就近落到的那一档。
|
|
6
|
+
*
|
|
7
|
+
* 模型目录(pi 的 `thinkingLevelMap`)和模型编辑器都用它把「支持哪几档」变成模型配置里的
|
|
8
|
+
* `thinkingOptions`。列表里没有 `off`,就表示这个模型关不掉思考。
|
|
9
|
+
*/
|
|
10
|
+
export declare function thinkingOptionsFor(levels: readonly ThinkingLevel[], preferredDefault?: ThinkingLevel): ThinkingOption[];
|
|
11
|
+
/**
|
|
12
|
+
* 没配置档位的模型用这一组:几乎所有推理接口都认的四档。
|
|
13
|
+
*
|
|
14
|
+
* 档位不再按模型名猜。按名字写死的表和厂商的实际能力对不上(给不认 `minimal` 的模型 `minimal`、
|
|
15
|
+
* 给关不掉思考的模型「关闭」),猜错了也不报错。真实档位从模型目录填入,或在模型编辑器里勾选。
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_THINKING_OPTIONS: ThinkingOption[];
|
|
18
|
+
/**
|
|
19
|
+
* 模型可选的档位:配置了就用配置的,否则用默认那一组;不支持思考的模型一档都没有。
|
|
20
|
+
*
|
|
21
|
+
* 关不掉思考的模型(`vendors/` 的 `alwaysOn`)不给「关闭」:适配器对它们什么都不发,选了「关闭」照样思考、
|
|
22
|
+
* 照样计费,界面却显示已关。`catalogFill` 填配置时已经去掉,但没配档位的(默认那一组带 `off`)、在那之前
|
|
23
|
+
* 保存的配置都还带着,所以在这里统一再收一次。这里只有模型名、没有端点,只按模型名的规则判。
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveModelThinkingOptions(model?: ModelConfig | null): ThinkingOption[];
|
|
26
|
+
/**
|
|
27
|
+
* 选中的档位在这个模型上实际是哪一档。
|
|
28
|
+
*
|
|
29
|
+
* 模型没有这一档时就近取:先往深找,再往浅找(和 pi 的 `clampThinkingLevel` 同一个规则)。
|
|
30
|
+
* 换模型后留下的「最高」落到新模型最深的那档,而不是退回默认的「中」;没有「关闭」的模型上
|
|
31
|
+
* 选「关闭」,落到它最浅的那档。只有不在标准序列里的自定义档位名才退回模型的默认档。
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveThinkingOption(level: ThinkingLevel, model?: ModelConfig | null): ThinkingOption | undefined;
|
|
34
|
+
/** 发给接口的推理档位;`undefined` 表示关闭思考。和界面显示的是同一档。 */
|
|
35
|
+
export declare function resolveReasoningEffort(level: ThinkingLevel | undefined, model?: ModelConfig | null): string | undefined;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具名在流进来、发出去两头的规整。三条链共用,各自的 adapter / encoder 只调这里。
|
|
3
|
+
*
|
|
4
|
+
* 模型给的名字不一定合规:空的、两头带空白的,gpt-oss 经 vLLM 还会给出 `read<|channel|>commentary`。
|
|
5
|
+
* 工具找不到不要紧——那一轮照常以「工具不存在」收场;要紧的是那次调用留在了历史里,而两种 OpenAI 格式
|
|
6
|
+
* 对 `function.name` 要求 `^[a-zA-Z0-9_-]+$` 且非空,原样回放就是之后每一轮 400,没有学习器认领,会话报废。
|
|
7
|
+
*/
|
|
8
|
+
/** 流里收到的工具名:两头的空白去掉。别的不动——名字认不认得是执行那一层的事(`agent/tool-args.ts`)。 */
|
|
9
|
+
export declare function toolNameFromStream(raw: unknown): string;
|
|
10
|
+
/**
|
|
11
|
+
* 回放历史里一次工具调用时发的名字。
|
|
12
|
+
*
|
|
13
|
+
* 合规的名字原样返回,一个字节都不动——这在每一轮的整段历史上跑一遍,改动合规的名字会让提示缓存作废。
|
|
14
|
+
* 不合规的逐字符替换而不是换成固定占位:`read__channel__commentary` 比 `unknown_tool` 更能让模型看出自己
|
|
15
|
+
* 上次叫错了什么。只改名字不改 id,调用和结果的配对不受影响;名字也不参与配对(三种格式的结果都只带 id)。
|
|
16
|
+
*/
|
|
17
|
+
export declare function toolNameForReplay(name: string): string;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一轮里的多个工具调用,`function_call` 和它们的结果该怎么排——问出来之后记住。
|
|
3
|
+
*
|
|
4
|
+
* 两家的要求直接冲突,一个全局形状满足不了:
|
|
5
|
+
*
|
|
6
|
+
* - `api.deepseek.com` 的 Responses:**成组**。所有 `function_call`,然后所有 `function_call_output`。
|
|
7
|
+
* 交错排会被拒,而且它报的是
|
|
8
|
+
* `The reasoning_text in the thinking mode must be passed back to the API.`——一句跟工具毫无关系的
|
|
9
|
+
* 话。推理项一个都不发时它报的还是这句,所以那不是字面意思,是它对工具配对问题的统一说法。
|
|
10
|
+
* - 把 Responses 翻译成 Chat Completions 的中转:**交错**。它们把每个 `function_call` 变成一条独立的
|
|
11
|
+
* 助手消息,而 Chat Completions 要求带 `tool_calls` 的消息后面紧跟回答它的 tool 消息,于是两个调用
|
|
12
|
+
* 连排就成了两条背靠背的助手消息:
|
|
13
|
+
* `an assistant message with 'tool_calls' must be followed by tool messages responding to each
|
|
14
|
+
* 'tool_call_id'. The following tool_call_ids did not have response messages: bash:0`
|
|
15
|
+
*
|
|
16
|
+
* 默认成组,不是因为哪家更重要,是因为**认错的代价不对称**:
|
|
17
|
+
*
|
|
18
|
+
* - 默认成组、撞上要交错的中转时,它给的错误明确点名 `tool_call_ids`,一看就知道该换哪个形状,学得准。
|
|
19
|
+
* - 默认交错、撞上要成组的端点时,它只会说「推理没带回来」。拿这句话去猜「其实是工具排错了」,猜不出来
|
|
20
|
+
* ——这次就是这么埋了两个报废的会话,而且把排查带偏了一整天。
|
|
21
|
+
*
|
|
22
|
+
* 成组也是 Responses 协议本来的样子:结果靠 `call_id` 找它的调用,不靠位置。OpenAI 官方两种都收。
|
|
23
|
+
*
|
|
24
|
+
* **这个默认值的证据覆盖面**(写下来是因为将来有人质疑它时,第一个该看的就是这个):2026-09-11 用
|
|
25
|
+
* `test/tool-pairing-survey-probe.ts` 普查了用户配置里的 2 个 Responses 端点,各发三次(单个调用做对照、
|
|
26
|
+
* 交错、成组)——
|
|
27
|
+
*
|
|
28
|
+
* - `api.deepseek.com` / `deepseek-flash`:对照 200,交错 400,成组 200 → **只收成组**
|
|
29
|
+
* - 某中转 / `claude-opus-4-6-thinking`:三发全 400,连单个调用的对照都拒,理由与排列无关 → 对照作废
|
|
30
|
+
*
|
|
31
|
+
* 也就是**有效样本 n=1**,而且没有任何现存端点被证明需要交错。要交错的那条路来自更早的一次真实报障
|
|
32
|
+
* (错误原文见上),那个端点已不在用户的配置里,所以它现在无法复验。样本这么小仍然改默认值,靠的是上面
|
|
33
|
+
* 那条不对称,不是样本量——这一点必须诚实写在这里。
|
|
34
|
+
*
|
|
35
|
+
* 和 `reasoning-compat.ts` 是**两个独立的轴**,各学各的。上一版把「发几块推理」和「一块里带哪些字段」
|
|
36
|
+
* 压进同一条梯子,结果是端点说 A、梯子退 B,对不上号的时候只能靠碰巧。同一个错误不犯第二次。
|
|
37
|
+
*
|
|
38
|
+
* 记在内存里,不落盘——理由同 `reasoning-compat.ts`:重启后重学一次的代价是一次自愈重发,换来不必做
|
|
39
|
+
* 设置迁移,也不必担心一条学错的结论永久粘在用户的配置里。
|
|
40
|
+
*/
|
|
41
|
+
/** 一轮里多个工具调用的排法。 */
|
|
42
|
+
export type ToolPairing = "grouped" | "interleaved";
|
|
43
|
+
/** 这个模型该怎么排。默认成组,理由见文件头。 */
|
|
44
|
+
export declare function toolPairing(providerId: string, modelId: string): ToolPairing;
|
|
45
|
+
/**
|
|
46
|
+
* 从一次失败里学点东西。返回「结论变了,值得换个形状重发一次」。
|
|
47
|
+
*
|
|
48
|
+
* 只有一个方向:成组 → 交错。反向不学——要成组的那家给的错误话里没有任何能指认排列问题的东西(见文件头),
|
|
49
|
+
* 拿它当信号只会误判。成组已经是默认,真需要反向时应该是有人拿着新证据来改这里,而不是让它自己猜。
|
|
50
|
+
*/
|
|
51
|
+
export declare function learnToolPairing(providerId: string, modelId: string, error: string): boolean;
|
|
52
|
+
/** 测试用:把学到的都忘掉。 */
|
|
53
|
+
export declare function resetToolPairingCompat(): void;
|