@sayknow-cli/ai 0.3.16 → 0.4.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/package.json +24 -25
- package/src/auth-gateway/server.ts +164 -22
- package/src/auth-storage.ts +76 -43
- package/src/context-cap-policy.ts +59 -0
- package/src/index.ts +2 -0
- package/src/model-cache.ts +10 -0
- package/src/model-manager.ts +30 -17
- package/src/model-thinking.ts +14 -8
- package/src/providers/amazon-bedrock.ts +9 -1
- package/src/providers/anthropic-messages-server.ts +178 -17
- package/src/providers/anthropic.ts +199 -213
- package/src/providers/azure-openai-responses.ts +6 -1
- package/src/providers/google-gemini-cli.ts +26 -13
- package/src/providers/google-shared.ts +7 -1
- package/src/providers/ollama.ts +7 -1
- package/src/providers/openai-chat-server.ts +93 -5
- package/src/providers/openai-codex/response-handler.ts +11 -3
- package/src/providers/openai-codex-responses.ts +107 -20
- package/src/providers/openai-responses-server.ts +111 -36
- package/src/providers/openai-responses-shared.ts +71 -12
- package/src/providers/openai-responses.ts +26 -3
- package/src/providers/pi-native-client.ts +24 -12
- package/src/providers/pi-native-server.ts +275 -21
- package/src/types.ts +53 -4
- package/src/utils/discovery/codex.ts +3 -12
- package/src/utils/event-stream.ts +146 -58
- package/src/utils/fallback-transport.ts +269 -0
- package/src/utils/overflow.ts +72 -31
- package/src/utils/retry.ts +2 -2
- package/src/utils/validation.ts +6 -1
- package/src/utils.ts +120 -12
- package/dist/types/api-registry.d.ts +0 -30
- package/dist/types/auth-broker/client.d.ts +0 -67
- package/dist/types/auth-broker/index.d.ts +0 -5
- package/dist/types/auth-broker/refresher.d.ts +0 -25
- package/dist/types/auth-broker/remote-store.d.ts +0 -99
- package/dist/types/auth-broker/server.d.ts +0 -32
- package/dist/types/auth-broker/types.d.ts +0 -110
- package/dist/types/auth-broker/wire-schemas.d.ts +0 -443
- package/dist/types/auth-gateway/http.d.ts +0 -40
- package/dist/types/auth-gateway/index.d.ts +0 -3
- package/dist/types/auth-gateway/server.d.ts +0 -17
- package/dist/types/auth-gateway/types.d.ts +0 -115
- package/dist/types/auth-storage.d.ts +0 -695
- package/dist/types/cli.d.ts +0 -2
- package/dist/types/index.d.ts +0 -51
- package/dist/types/model-cache.d.ts +0 -17
- package/dist/types/model-manager.d.ts +0 -62
- package/dist/types/model-retirements.d.ts +0 -6
- package/dist/types/model-thinking.d.ts +0 -74
- package/dist/types/models.d.ts +0 -21
- package/dist/types/provider-details.d.ts +0 -24
- package/dist/types/provider-models/bundled-references.d.ts +0 -4
- package/dist/types/provider-models/descriptors.d.ts +0 -48
- package/dist/types/provider-models/google.d.ts +0 -20
- package/dist/types/provider-models/index.d.ts +0 -5
- package/dist/types/provider-models/ollama.d.ts +0 -7
- package/dist/types/provider-models/openai-compat.d.ts +0 -256
- package/dist/types/provider-models/special.d.ts +0 -19
- package/dist/types/providers/amazon-bedrock.d.ts +0 -60
- package/dist/types/providers/anthropic-messages-server-schema.d.ts +0 -450
- package/dist/types/providers/anthropic-messages-server.d.ts +0 -17
- package/dist/types/providers/anthropic.d.ts +0 -208
- package/dist/types/providers/aws-credential-config.d.ts +0 -19
- package/dist/types/providers/aws-credentials.d.ts +0 -43
- package/dist/types/providers/aws-eventstream.d.ts +0 -38
- package/dist/types/providers/aws-sigv4.d.ts +0 -55
- package/dist/types/providers/azure-openai-responses.d.ts +0 -15
- package/dist/types/providers/composer-discipline.d.ts +0 -26
- package/dist/types/providers/cursor/client-version.d.ts +0 -10
- package/dist/types/providers/cursor/gen/agent_pb.d.ts +0 -13022
- package/dist/types/providers/cursor.d.ts +0 -45
- package/dist/types/providers/error-message.d.ts +0 -27
- package/dist/types/providers/github-copilot-headers.d.ts +0 -40
- package/dist/types/providers/gitlab-duo.d.ts +0 -27
- package/dist/types/providers/google-auth.d.ts +0 -24
- package/dist/types/providers/google-gemini-cli.d.ts +0 -75
- package/dist/types/providers/google-gemini-headers.d.ts +0 -43
- package/dist/types/providers/google-shared.d.ts +0 -179
- package/dist/types/providers/google-types.d.ts +0 -138
- package/dist/types/providers/google-vertex.d.ts +0 -7
- package/dist/types/providers/google.d.ts +0 -4
- package/dist/types/providers/grammar.d.ts +0 -1
- package/dist/types/providers/kimi.d.ts +0 -27
- package/dist/types/providers/mock.d.ts +0 -177
- package/dist/types/providers/ollama.d.ts +0 -41
- package/dist/types/providers/openai-anthropic-shim.d.ts +0 -31
- package/dist/types/providers/openai-bounded-rate-limits.d.ts +0 -3
- package/dist/types/providers/openai-chat-server-schema.d.ts +0 -815
- package/dist/types/providers/openai-chat-server.d.ts +0 -16
- package/dist/types/providers/openai-codex/constants.d.ts +0 -26
- package/dist/types/providers/openai-codex/request-transformer.d.ts +0 -50
- package/dist/types/providers/openai-codex/response-handler.d.ts +0 -17
- package/dist/types/providers/openai-codex-responses.d.ts +0 -67
- package/dist/types/providers/openai-completions-compat.d.ts +0 -27
- package/dist/types/providers/openai-completions.d.ts +0 -33
- package/dist/types/providers/openai-request-transform.d.ts +0 -4
- package/dist/types/providers/openai-responses-server-schema.d.ts +0 -392
- package/dist/types/providers/openai-responses-server.d.ts +0 -17
- package/dist/types/providers/openai-responses-shared.d.ts +0 -104
- package/dist/types/providers/openai-responses.d.ts +0 -32
- package/dist/types/providers/pi-native-client.d.ts +0 -13
- package/dist/types/providers/pi-native-server.d.ts +0 -68
- package/dist/types/providers/register-builtins.d.ts +0 -31
- package/dist/types/providers/synthetic.d.ts +0 -26
- package/dist/types/providers/transform-messages.d.ts +0 -14
- package/dist/types/providers/vision-guard.d.ts +0 -8
- package/dist/types/rate-limit-utils.d.ts +0 -19
- package/dist/types/stream.d.ts +0 -43
- package/dist/types/types.d.ts +0 -831
- package/dist/types/usage/claude.d.ts +0 -3
- package/dist/types/usage/gemini.d.ts +0 -2
- package/dist/types/usage/github-copilot.d.ts +0 -7
- package/dist/types/usage/google-antigravity.d.ts +0 -2
- package/dist/types/usage/grok-cli.d.ts +0 -10
- package/dist/types/usage/kimi.d.ts +0 -2
- package/dist/types/usage/minimax-code.d.ts +0 -2
- package/dist/types/usage/openai-codex.d.ts +0 -3
- package/dist/types/usage/shared.d.ts +0 -1
- package/dist/types/usage/zai.d.ts +0 -2
- package/dist/types/usage.d.ts +0 -258
- package/dist/types/utils/abort.d.ts +0 -19
- package/dist/types/utils/anthropic-auth.d.ts +0 -31
- package/dist/types/utils/discovery/antigravity.d.ts +0 -67
- package/dist/types/utils/discovery/codex.d.ts +0 -38
- package/dist/types/utils/discovery/cursor.d.ts +0 -23
- package/dist/types/utils/discovery/gemini.d.ts +0 -25
- package/dist/types/utils/discovery/index.d.ts +0 -4
- package/dist/types/utils/discovery/openai-compatible.d.ts +0 -74
- package/dist/types/utils/event-stream.d.ts +0 -33
- package/dist/types/utils/fireworks-model-id.d.ts +0 -10
- package/dist/types/utils/foundry.d.ts +0 -1
- package/dist/types/utils/h2-fetch.d.ts +0 -22
- package/dist/types/utils/http-inspector.d.ts +0 -35
- package/dist/types/utils/idle-iterator.d.ts +0 -67
- package/dist/types/utils/json-parse.d.ts +0 -18
- package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
- package/dist/types/utils/oauth/anthropic.d.ts +0 -22
- package/dist/types/utils/oauth/api-key-login.d.ts +0 -35
- package/dist/types/utils/oauth/api-key-validation.d.ts +0 -27
- package/dist/types/utils/oauth/callback-server.d.ts +0 -60
- package/dist/types/utils/oauth/cerebras.d.ts +0 -1
- package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +0 -18
- package/dist/types/utils/oauth/cursor.d.ts +0 -15
- package/dist/types/utils/oauth/deepinfra.d.ts +0 -1
- package/dist/types/utils/oauth/deepseek.d.ts +0 -10
- package/dist/types/utils/oauth/firepass.d.ts +0 -1
- package/dist/types/utils/oauth/fireworks.d.ts +0 -1
- package/dist/types/utils/oauth/fugu.d.ts +0 -1
- package/dist/types/utils/oauth/github-copilot.d.ts +0 -38
- package/dist/types/utils/oauth/gitlab-duo.d.ts +0 -3
- package/dist/types/utils/oauth/glm-zcode.d.ts +0 -71
- package/dist/types/utils/oauth/google-antigravity.d.ts +0 -11
- package/dist/types/utils/oauth/google-gemini-cli.d.ts +0 -10
- package/dist/types/utils/oauth/google-oauth-shared.d.ts +0 -28
- package/dist/types/utils/oauth/huggingface.d.ts +0 -19
- package/dist/types/utils/oauth/index.d.ts +0 -39
- package/dist/types/utils/oauth/kagi.d.ts +0 -17
- package/dist/types/utils/oauth/kilo.d.ts +0 -5
- package/dist/types/utils/oauth/kimi.d.ts +0 -21
- package/dist/types/utils/oauth/litellm.d.ts +0 -18
- package/dist/types/utils/oauth/lm-studio.d.ts +0 -17
- package/dist/types/utils/oauth/minimax-code.d.ts +0 -28
- package/dist/types/utils/oauth/moonshot.d.ts +0 -1
- package/dist/types/utils/oauth/nanogpt.d.ts +0 -1
- package/dist/types/utils/oauth/nvidia.d.ts +0 -18
- package/dist/types/utils/oauth/ollama-cloud.d.ts +0 -2
- package/dist/types/utils/oauth/ollama.d.ts +0 -18
- package/dist/types/utils/oauth/openai-codex.d.ts +0 -21
- package/dist/types/utils/oauth/opencode.d.ts +0 -18
- package/dist/types/utils/oauth/parallel.d.ts +0 -17
- package/dist/types/utils/oauth/perplexity.d.ts +0 -9
- package/dist/types/utils/oauth/pkce.d.ts +0 -8
- package/dist/types/utils/oauth/qianfan.d.ts +0 -17
- package/dist/types/utils/oauth/qwen-portal.d.ts +0 -19
- package/dist/types/utils/oauth/synthetic.d.ts +0 -1
- package/dist/types/utils/oauth/tavily.d.ts +0 -17
- package/dist/types/utils/oauth/together.d.ts +0 -1
- package/dist/types/utils/oauth/types.d.ts +0 -45
- package/dist/types/utils/oauth/venice.d.ts +0 -18
- package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +0 -18
- package/dist/types/utils/oauth/vllm.d.ts +0 -16
- package/dist/types/utils/oauth/xai.d.ts +0 -30
- package/dist/types/utils/oauth/xiaomi.d.ts +0 -25
- package/dist/types/utils/oauth/zai.d.ts +0 -18
- package/dist/types/utils/oauth/zenmux.d.ts +0 -1
- package/dist/types/utils/overflow.d.ts +0 -64
- package/dist/types/utils/parse-bind.d.ts +0 -23
- package/dist/types/utils/provider-response.d.ts +0 -3
- package/dist/types/utils/retry-after.d.ts +0 -3
- package/dist/types/utils/retry-budget.d.ts +0 -1
- package/dist/types/utils/retry.d.ts +0 -26
- package/dist/types/utils/schema/adapt.d.ts +0 -24
- package/dist/types/utils/schema/compatibility.d.ts +0 -30
- package/dist/types/utils/schema/dereference.d.ts +0 -11
- package/dist/types/utils/schema/draft.d.ts +0 -10
- package/dist/types/utils/schema/equality.d.ts +0 -4
- package/dist/types/utils/schema/fields.d.ts +0 -49
- package/dist/types/utils/schema/index.d.ts +0 -14
- package/dist/types/utils/schema/json-schema-validator.d.ts +0 -12
- package/dist/types/utils/schema/meta-validator.d.ts +0 -2
- package/dist/types/utils/schema/normalize.d.ts +0 -93
- package/dist/types/utils/schema/root-combinator.d.ts +0 -12
- package/dist/types/utils/schema/spill.d.ts +0 -8
- package/dist/types/utils/schema/stamps.d.ts +0 -25
- package/dist/types/utils/schema/types.d.ts +0 -4
- package/dist/types/utils/schema/wire.d.ts +0 -54
- package/dist/types/utils/schema/zod-decontaminate.d.ts +0 -31
- package/dist/types/utils/sse-debug.d.ts +0 -10
- package/dist/types/utils/tool-call-healing.d.ts +0 -71
- package/dist/types/utils/tool-choice-capability.d.ts +0 -41
- package/dist/types/utils/tool-choice.d.ts +0 -50
- package/dist/types/utils/validation.d.ts +0 -17
- package/dist/types/utils.d.ts +0 -35
|
@@ -95,12 +95,20 @@ let warnedReasoningSummaryLevel = false;
|
|
|
95
95
|
|
|
96
96
|
// ─── inbound parser helpers ─────────────────────────────────────────────────
|
|
97
97
|
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
function reasoningContentFromItem(
|
|
99
|
+
item: OpenAIResponsesReasoningItem,
|
|
100
|
+
): Pick<ThinkingContent, "thinking" | "provenance" | "summaryText" | "rawText"> {
|
|
101
|
+
// `summary[]` is provider-displayable; `content[]` is raw reasoning. Keep
|
|
102
|
+
// these channels distinct so a Responses gateway round-trip cannot relabel
|
|
103
|
+
// raw CoT as a summary merely because summary text is absent.
|
|
104
|
+
const summaryText = (item.summary ?? []).map(part => part.text).join("");
|
|
105
|
+
const rawText = (item.content ?? []).map(part => part.text).join("");
|
|
106
|
+
if (summaryText && rawText) {
|
|
107
|
+
return { thinking: summaryText, provenance: "mixed", summaryText, rawText };
|
|
108
|
+
}
|
|
109
|
+
if (summaryText) return { thinking: summaryText, provenance: "summary", summaryText };
|
|
110
|
+
if (rawText) return { thinking: rawText, provenance: "raw", rawText };
|
|
111
|
+
return { thinking: "" };
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
type InputBlockUnion =
|
|
@@ -335,10 +343,10 @@ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
|
|
|
335
343
|
}
|
|
336
344
|
if (effectiveType === "reasoning") {
|
|
337
345
|
const reasoning = item as OpenAIResponsesReasoningItem;
|
|
338
|
-
const
|
|
346
|
+
const content = reasoningContentFromItem(reasoning);
|
|
339
347
|
const thinking: ThinkingContent = {
|
|
340
348
|
type: "thinking",
|
|
341
|
-
|
|
349
|
+
...content,
|
|
342
350
|
thinkingSignature: JSON.stringify(reasoning),
|
|
343
351
|
...(reasoning.id ? { itemId: reasoning.id } : {}),
|
|
344
352
|
};
|
|
@@ -538,6 +546,41 @@ function responseStatusForStopReason(message: AssistantMessage): ResponseStatus
|
|
|
538
546
|
return "completed";
|
|
539
547
|
}
|
|
540
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Privacy boundary for the public Responses envelope: a reasoning item's
|
|
551
|
+
* `summary_text` must carry ONLY provider-displayable summary text, never raw
|
|
552
|
+
* chain-of-thought. A summary is published ONLY for blocks explicitly marked
|
|
553
|
+
* `provenance: "summary" | "mixed"` (the #2304 provenance path), sourced from
|
|
554
|
+
* `summaryText`. Raw-provenance AND unmarked blocks are omitted: unmarked
|
|
555
|
+
* `thinking` can be raw CoT from providers that stream unmarked reasoning (e.g.
|
|
556
|
+
* openai-completions / ollama) which the auth gateway re-encodes into the
|
|
557
|
+
* Responses wire format, so falling open to `part.thinking` would leak raw CoT.
|
|
558
|
+
*/
|
|
559
|
+
function envelopeSummaryText(part: ThinkingContent): string | undefined {
|
|
560
|
+
if (part.provenance === "summary" || part.provenance === "mixed") return part.summaryText;
|
|
561
|
+
return undefined;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function envelopeSummaryParts(part: ThinkingContent): Array<{ type: "summary_text"; text: string }> {
|
|
565
|
+
const text = envelopeSummaryText(part);
|
|
566
|
+
return text ? [{ type: "summary_text", text }] : [];
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function normalizeSummaryParts(value: unknown): Array<{ type: "summary_text"; text: string }> {
|
|
570
|
+
// A serialized signature's `summary` is, by the Responses protocol, provider-
|
|
571
|
+
// displayable summary text (raw reasoning lives in content[]/encrypted_content,
|
|
572
|
+
// which is stripped). Coerce to the canonical shape, keeping only well-formed
|
|
573
|
+
// summary_text entries. This is NOT the unsafe `part.thinking` fallback.
|
|
574
|
+
if (!Array.isArray(value)) return [];
|
|
575
|
+
const out: Array<{ type: "summary_text"; text: string }> = [];
|
|
576
|
+
for (const entry of value) {
|
|
577
|
+
if (isObj(entry) && entry.type === "summary_text" && typeof entry.text === "string") {
|
|
578
|
+
out.push({ type: "summary_text", text: entry.text });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return out;
|
|
582
|
+
}
|
|
583
|
+
|
|
541
584
|
function buildReasoningItem(part: ThinkingContent): ReasoningOutputItem {
|
|
542
585
|
const baseId = part.itemId ?? makeReasoningId();
|
|
543
586
|
if (part.thinkingSignature) {
|
|
@@ -548,10 +591,15 @@ function buildReasoningItem(part: ThinkingContent): ReasoningOutputItem {
|
|
|
548
591
|
// Preserve any extra fields (encrypted_content, …) the original carried,
|
|
549
592
|
// but normalize the summary into the canonical `{type, text}[]` shape.
|
|
550
593
|
const merged: Record<string, unknown> = { ...sigParsed, type: "reasoning", id };
|
|
551
|
-
merged.summary =
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
594
|
+
merged.summary =
|
|
595
|
+
part.provenance === "summary" || part.provenance === "mixed"
|
|
596
|
+
? envelopeSummaryParts(part)
|
|
597
|
+
: normalizeSummaryParts(sigParsed.summary);
|
|
598
|
+
// Strip any `content[]` (raw `reasoning_text`) the serialized signature
|
|
599
|
+
// carried: raw chain-of-thought must never surface in the public final
|
|
600
|
+
// envelope (#2304 CoT boundary). Opaque top-level `encrypted_content`
|
|
601
|
+
// (when present) is a separate field and is preserved by the spread above.
|
|
602
|
+
delete merged.content;
|
|
555
603
|
return merged as ReasoningOutputItem;
|
|
556
604
|
}
|
|
557
605
|
} catch {
|
|
@@ -561,7 +609,7 @@ function buildReasoningItem(part: ThinkingContent): ReasoningOutputItem {
|
|
|
561
609
|
return {
|
|
562
610
|
type: "reasoning",
|
|
563
611
|
id: baseId,
|
|
564
|
-
summary:
|
|
612
|
+
summary: envelopeSummaryParts(part),
|
|
565
613
|
};
|
|
566
614
|
}
|
|
567
615
|
|
|
@@ -701,7 +749,8 @@ interface OpenReasoning {
|
|
|
701
749
|
kind: "reasoning";
|
|
702
750
|
itemId: string;
|
|
703
751
|
outputIndex: number;
|
|
704
|
-
|
|
752
|
+
summaryText: string;
|
|
753
|
+
summaryPartText: string;
|
|
705
754
|
}
|
|
706
755
|
interface OpenFunctionCall {
|
|
707
756
|
kind: "function_call";
|
|
@@ -781,16 +830,13 @@ export function encodeStream(
|
|
|
781
830
|
summary: [] as Array<{ type: "summary_text"; text: string }>,
|
|
782
831
|
};
|
|
783
832
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
part: { type: "summary_text", text: "" },
|
|
792
|
-
});
|
|
793
|
-
const next: OpenReasoning = { kind: "reasoning", itemId, outputIndex, reasoningText: "" };
|
|
833
|
+
const next: OpenReasoning = {
|
|
834
|
+
kind: "reasoning",
|
|
835
|
+
itemId,
|
|
836
|
+
outputIndex,
|
|
837
|
+
summaryText: "",
|
|
838
|
+
summaryPartText: "",
|
|
839
|
+
};
|
|
794
840
|
state.open = next;
|
|
795
841
|
return next;
|
|
796
842
|
};
|
|
@@ -856,18 +902,21 @@ export function encodeStream(
|
|
|
856
902
|
content: state.open.content,
|
|
857
903
|
});
|
|
858
904
|
} else if (state.open.kind === "reasoning") {
|
|
859
|
-
const summary =
|
|
860
|
-
|
|
905
|
+
const summary = state.open.summaryText
|
|
906
|
+
? [{ type: "summary_text" as const, text: state.open.summaryText }]
|
|
907
|
+
: [];
|
|
908
|
+
// Final reasoning envelope carries the displayable summary ONLY. Raw
|
|
909
|
+
// chain-of-thought is streamed live via response.reasoning_text.delta
|
|
910
|
+
// (the internal raw channel) and is deliberately NOT persisted into the
|
|
911
|
+
// terminal item's content[] — the public final envelope must never carry
|
|
912
|
+
// raw CoT (#2304 CoT boundary).
|
|
913
|
+
const item: ReasoningOutputItem = {
|
|
861
914
|
type: "reasoning",
|
|
862
915
|
id: state.open.itemId,
|
|
863
916
|
summary,
|
|
864
917
|
};
|
|
865
918
|
emit("response.output_item.done", { output_index: state.open.outputIndex, item });
|
|
866
|
-
finishedItems.push(
|
|
867
|
-
type: "reasoning",
|
|
868
|
-
id: state.open.itemId,
|
|
869
|
-
summary,
|
|
870
|
-
});
|
|
919
|
+
finishedItems.push(item);
|
|
871
920
|
} else {
|
|
872
921
|
const text = state.open.argsText ?? "";
|
|
873
922
|
if (state.open.customWireName) {
|
|
@@ -1006,9 +1055,34 @@ export function encodeStream(
|
|
|
1006
1055
|
break;
|
|
1007
1056
|
}
|
|
1008
1057
|
case "thinking_delta": {
|
|
1058
|
+
// Raw reasoning is private. The public Responses gateway emits only
|
|
1059
|
+
// provider-displayable reasoning_summary_* events.
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
case "thinking_end": {
|
|
1063
|
+
if (state.open?.kind !== "reasoning") break;
|
|
1064
|
+
// Raw reasoning is intentionally omitted from every public gateway
|
|
1065
|
+
// frame. Only reasoning_summary_* events populate the terminal item.
|
|
1066
|
+
closeOpen();
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
case "reasoning_summary_start": {
|
|
1009
1070
|
if (state.open?.kind !== "reasoning") break;
|
|
1010
1071
|
const cur: OpenReasoning = state.open;
|
|
1011
|
-
cur.
|
|
1072
|
+
cur.summaryPartText = "";
|
|
1073
|
+
emit("response.reasoning_summary_part.added", {
|
|
1074
|
+
item_id: cur.itemId,
|
|
1075
|
+
output_index: cur.outputIndex,
|
|
1076
|
+
summary_index: 0,
|
|
1077
|
+
part: { type: "summary_text", text: "" },
|
|
1078
|
+
});
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
case "reasoning_summary_delta": {
|
|
1082
|
+
if (state.open?.kind !== "reasoning") break;
|
|
1083
|
+
const cur: OpenReasoning = state.open;
|
|
1084
|
+
cur.summaryPartText += ev.delta;
|
|
1085
|
+
cur.summaryText += ev.delta;
|
|
1012
1086
|
emit("response.reasoning_summary_text.delta", {
|
|
1013
1087
|
item_id: cur.itemId,
|
|
1014
1088
|
output_index: cur.outputIndex,
|
|
@@ -1017,11 +1091,13 @@ export function encodeStream(
|
|
|
1017
1091
|
});
|
|
1018
1092
|
break;
|
|
1019
1093
|
}
|
|
1020
|
-
case "
|
|
1094
|
+
case "reasoning_summary_end": {
|
|
1021
1095
|
if (state.open?.kind !== "reasoning") break;
|
|
1022
1096
|
const cur: OpenReasoning = state.open;
|
|
1023
|
-
const text = ev.content ?? cur.
|
|
1024
|
-
|
|
1097
|
+
const text = ev.content ?? cur.summaryPartText;
|
|
1098
|
+
// A separator-only accumulated summary (e.g. a part.done "\n\n" before any
|
|
1099
|
+
// real text) is treated as empty so the real end content wins.
|
|
1100
|
+
if (!cur.summaryText.trim()) cur.summaryText = text;
|
|
1025
1101
|
emit("response.reasoning_summary_text.done", {
|
|
1026
1102
|
item_id: cur.itemId,
|
|
1027
1103
|
output_index: cur.outputIndex,
|
|
@@ -1034,7 +1110,6 @@ export function encodeStream(
|
|
|
1034
1110
|
summary_index: 0,
|
|
1035
1111
|
part: { type: "summary_text", text },
|
|
1036
1112
|
});
|
|
1037
|
-
closeOpen();
|
|
1038
1113
|
break;
|
|
1039
1114
|
}
|
|
1040
1115
|
case "toolcall_start": {
|
|
@@ -376,6 +376,9 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
376
376
|
item: StreamItem;
|
|
377
377
|
block: StreamBlock;
|
|
378
378
|
blockContentIndex: number;
|
|
379
|
+
summaryBuffer: string;
|
|
380
|
+
rawBuffer: string;
|
|
381
|
+
summaryStarted: boolean;
|
|
379
382
|
}
|
|
380
383
|
// Per-item argument buffer keyed on stable item identity. Multiple tool-call
|
|
381
384
|
// items can stream interleaved argument deltas in one response, so a single
|
|
@@ -412,7 +415,14 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
412
415
|
};
|
|
413
416
|
const registerEntry = (item: StreamItem, block: StreamBlock, outputIndex: number | undefined): ItemEntry => {
|
|
414
417
|
output.content.push(block);
|
|
415
|
-
const entry: ItemEntry = {
|
|
418
|
+
const entry: ItemEntry = {
|
|
419
|
+
item,
|
|
420
|
+
block,
|
|
421
|
+
blockContentIndex: output.content.length - 1,
|
|
422
|
+
summaryBuffer: "",
|
|
423
|
+
rawBuffer: "",
|
|
424
|
+
summaryStarted: false,
|
|
425
|
+
};
|
|
416
426
|
// Primary key prefers the stable item id; if the wire omits it, fall back to
|
|
417
427
|
// the positional index. A synthetic key keeps the entry addressable as lastKey
|
|
418
428
|
// for continuation-style non-tool events even when neither is present.
|
|
@@ -480,9 +490,13 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
480
490
|
}
|
|
481
491
|
} else if (event.type === "response.reasoning_summary_part.added") {
|
|
482
492
|
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
483
|
-
if (entry?.item.type === "reasoning") {
|
|
493
|
+
if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
|
|
484
494
|
entry.item.summary = entry.item.summary || [];
|
|
485
495
|
entry.item.summary.push(event.part);
|
|
496
|
+
if (!entry.summaryStarted) {
|
|
497
|
+
entry.summaryStarted = true;
|
|
498
|
+
stream.push({ type: "reasoning_summary_start", contentIndex: entry.blockContentIndex, partial: output });
|
|
499
|
+
}
|
|
486
500
|
}
|
|
487
501
|
} else if (event.type === "response.reasoning_summary_text.delta") {
|
|
488
502
|
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
@@ -491,9 +505,10 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
491
505
|
const lastPart = entry.item.summary[entry.item.summary.length - 1];
|
|
492
506
|
if (lastPart) {
|
|
493
507
|
entry.block.thinking += event.delta;
|
|
508
|
+
entry.summaryBuffer += event.delta;
|
|
494
509
|
lastPart.text += event.delta;
|
|
495
510
|
stream.push({
|
|
496
|
-
type: "
|
|
511
|
+
type: "reasoning_summary_delta",
|
|
497
512
|
contentIndex: entry.blockContentIndex,
|
|
498
513
|
delta: event.delta,
|
|
499
514
|
partial: output,
|
|
@@ -507,9 +522,10 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
507
522
|
const lastPart = entry.item.summary[entry.item.summary.length - 1];
|
|
508
523
|
if (lastPart) {
|
|
509
524
|
entry.block.thinking += "\n\n";
|
|
525
|
+
entry.summaryBuffer += "\n\n";
|
|
510
526
|
lastPart.text += "\n\n";
|
|
511
527
|
stream.push({
|
|
512
|
-
type: "
|
|
528
|
+
type: "reasoning_summary_delta",
|
|
513
529
|
contentIndex: entry.blockContentIndex,
|
|
514
530
|
delta: "\n\n",
|
|
515
531
|
partial: output,
|
|
@@ -522,6 +538,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
522
538
|
const entry = resolveEntry(event.item_id, event.output_index, "always");
|
|
523
539
|
if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
|
|
524
540
|
entry.block.thinking += event.delta;
|
|
541
|
+
entry.rawBuffer += event.delta;
|
|
525
542
|
stream.push({
|
|
526
543
|
type: "thinking_delta",
|
|
527
544
|
contentIndex: entry.blockContentIndex,
|
|
@@ -608,12 +625,15 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
608
625
|
options?.onOutputItemDone?.(item);
|
|
609
626
|
const entry = resolveEntry(item.id, event.output_index, "never");
|
|
610
627
|
if (item.type === "reasoning") {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
628
|
+
// Prefer the streamed summary buffer only when it carries real text. When it
|
|
629
|
+
// holds only synthetic separators (e.g. a part.done arrived before/without any
|
|
630
|
+
// summary_text delta), fall back to the canonical `item.summary` from
|
|
631
|
+
// output_item.done so the materialized summaryText is not blank/separator-only.
|
|
632
|
+
const bufferSummary = entry?.summaryBuffer ?? "";
|
|
633
|
+
const itemSummary = item.summary?.map(part => part.text).join("\n\n") ?? "";
|
|
634
|
+
const summaryText = bufferSummary.trim() ? bufferSummary : itemSummary;
|
|
635
|
+
const rawText =
|
|
636
|
+
entry?.rawBuffer || (item.content?.[0]?.type === "reasoning_text" ? (item.content[0].text ?? "") : "");
|
|
617
637
|
const reasoningBlock =
|
|
618
638
|
entry?.block.type === "thinking"
|
|
619
639
|
? entry.block
|
|
@@ -621,14 +641,53 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
621
641
|
| ThinkingContent
|
|
622
642
|
| undefined);
|
|
623
643
|
if (reasoningBlock) {
|
|
624
|
-
|
|
644
|
+
const mutable = reasoningBlock as {
|
|
645
|
+
provenance?: "summary" | "raw" | "mixed";
|
|
646
|
+
summaryText?: string;
|
|
647
|
+
rawText?: string;
|
|
648
|
+
};
|
|
649
|
+
if (mutable.provenance === undefined) {
|
|
650
|
+
if (mutable.summaryText === undefined && summaryText) mutable.summaryText = summaryText;
|
|
651
|
+
if (mutable.rawText === undefined && rawText) mutable.rawText = rawText;
|
|
652
|
+
mutable.provenance =
|
|
653
|
+
summaryText && rawText ? "mixed" : summaryText ? "summary" : rawText ? "raw" : undefined;
|
|
654
|
+
}
|
|
655
|
+
// Finalized display string must exclude raw CoT when a summary exists.
|
|
656
|
+
// Derive it from the STORED write-once provenance fields (falling back to
|
|
657
|
+
// this event's locals only for a first classification) so a later or
|
|
658
|
+
// duplicate finalization carrying only raw can never overwrite a summary/
|
|
659
|
+
// mixed block's safe display with raw CoT. Raw-only stays raw.
|
|
660
|
+
{
|
|
661
|
+
const effSummary = mutable.summaryText ?? summaryText;
|
|
662
|
+
const effRaw = mutable.rawText ?? rawText;
|
|
663
|
+
reasoningBlock.thinking = mutable.provenance === "raw" ? effRaw : effSummary || effRaw;
|
|
664
|
+
}
|
|
625
665
|
reasoningBlock.thinkingSignature = JSON.stringify(item);
|
|
626
666
|
const reasoningBlockIndex =
|
|
627
667
|
entry?.block === reasoningBlock ? entry.blockContentIndex : output.content.indexOf(reasoningBlock);
|
|
668
|
+
if (summaryText) {
|
|
669
|
+
// If the summary text came only from the canonical item.summary (no
|
|
670
|
+
// streamed summary deltas/part.added), no reasoning_summary_start was
|
|
671
|
+
// emitted. Emit one now so consumers that open a summary on start
|
|
672
|
+
// (e.g. the Responses SSE encoder) don't receive an orphaned end.
|
|
673
|
+
if (!entry?.summaryStarted) {
|
|
674
|
+
stream.push({
|
|
675
|
+
type: "reasoning_summary_start",
|
|
676
|
+
contentIndex: reasoningBlockIndex,
|
|
677
|
+
partial: output,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
stream.push({
|
|
681
|
+
type: "reasoning_summary_end",
|
|
682
|
+
contentIndex: reasoningBlockIndex,
|
|
683
|
+
content: summaryText,
|
|
684
|
+
partial: output,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
628
687
|
stream.push({
|
|
629
688
|
type: "thinking_end",
|
|
630
689
|
contentIndex: reasoningBlockIndex,
|
|
631
|
-
content: thinking,
|
|
690
|
+
content: reasoningBlock.thinking,
|
|
632
691
|
partial: output,
|
|
633
692
|
});
|
|
634
693
|
}
|
|
@@ -33,12 +33,15 @@ import {
|
|
|
33
33
|
createOpenAIResponsesHistoryPayload,
|
|
34
34
|
getOpenAIResponsesHistoryItems,
|
|
35
35
|
getOpenAIResponsesHistoryPayload,
|
|
36
|
+
isInvalidPromptError,
|
|
37
|
+
neutralizeResponsesInputControlTokens,
|
|
36
38
|
normalizeSystemPrompts,
|
|
37
39
|
resolveCacheRetention,
|
|
38
40
|
sanitizeOpenAIResponsesHistoryItemsForReplay,
|
|
39
41
|
} from "../utils";
|
|
40
42
|
import { createAbortSourceTracker } from "../utils/abort";
|
|
41
43
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
44
|
+
import { transportFailureFacts } from "../utils/fallback-transport";
|
|
42
45
|
import { finalizeErrorMessage, type RawHttpRequestDump, rewriteCopilotError } from "../utils/http-inspector";
|
|
43
46
|
import {
|
|
44
47
|
createWatchdog,
|
|
@@ -298,9 +301,12 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
298
301
|
await notifyProviderResponse(options, response, model, request_id);
|
|
299
302
|
return data;
|
|
300
303
|
},
|
|
301
|
-
{ provider: model.provider, signal: requestSignal },
|
|
304
|
+
{ provider: model.provider, signal: requestSignal, fallbackManaged: options?.fallbackManaged },
|
|
302
305
|
).catch(async error => {
|
|
303
|
-
if (
|
|
306
|
+
if (
|
|
307
|
+
options?.fallbackManaged ||
|
|
308
|
+
!isForcedToolChoiceUnsupportedError(error, isForcedOpenAIResponsesToolChoice(params.tool_choice))
|
|
309
|
+
) {
|
|
304
310
|
throw error;
|
|
305
311
|
}
|
|
306
312
|
const reason = await finalizeErrorMessage(error, rawRequestDump);
|
|
@@ -379,8 +385,25 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
|
|
|
379
385
|
const firstEventTimeoutError = abortTracker.getLocalAbortReason();
|
|
380
386
|
output.stopReason = abortTracker.wasCallerAbort() ? "aborted" : "error";
|
|
381
387
|
output.errorStatus = extractHttpStatusFromError(error);
|
|
388
|
+
output.transportFailure = transportFailureFacts(error);
|
|
382
389
|
output.errorMessage = firstEventTimeoutError?.message ?? (await finalizeErrorMessage(error, rawRequestDump));
|
|
383
390
|
output.errorMessage = rewriteCopilotError(output.errorMessage, error, model.provider);
|
|
391
|
+
// Explicitly mark the poisoned-history rejection so the shared
|
|
392
|
+
// `invalid_prompt` contract is present even when the SDK error surfaces
|
|
393
|
+
// only a message (no structured code). This keeps the responses
|
|
394
|
+
// transport's classification uniform with the codex transport's
|
|
395
|
+
// non-retryable event set and lets the session-level circuit breaker
|
|
396
|
+
// key on one durable marker instead of per-transport string matching.
|
|
397
|
+
if (
|
|
398
|
+
output.stopReason === "error" &&
|
|
399
|
+
!output.transportFailure?.providerCode &&
|
|
400
|
+
(isInvalidPromptError(error) || isInvalidPromptError(output.errorMessage))
|
|
401
|
+
) {
|
|
402
|
+
output.transportFailure = {
|
|
403
|
+
...(output.transportFailure ?? { kind: "transport" }),
|
|
404
|
+
providerCode: "invalid_prompt",
|
|
405
|
+
};
|
|
406
|
+
}
|
|
384
407
|
output.duration = Date.now() - startTime;
|
|
385
408
|
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
386
409
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
@@ -493,7 +516,7 @@ function buildParams(
|
|
|
493
516
|
strictResponsesPairing,
|
|
494
517
|
providerSessionState,
|
|
495
518
|
);
|
|
496
|
-
const messages: ResponseInput =
|
|
519
|
+
const messages: ResponseInput = neutralizeResponsesInputControlTokens(conversationMessages);
|
|
497
520
|
|
|
498
521
|
const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
|
|
499
522
|
if (isComposerHarnessModel(model.id)) {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Activated when a {@link Model} has `transport: "pi-native"` set; the
|
|
13
13
|
* dispatch hook lives in `streamSimple()` (see `../stream.ts`). Used by
|
|
14
|
-
* containerized
|
|
15
|
-
*
|
|
14
|
+
* containerized SKC deployments that route every LLM call through a
|
|
15
|
+
* credential-holding sidecar so the container stays credential-free.
|
|
16
16
|
*/
|
|
17
17
|
import { readSseJson } from "@sayknow-cli/utils";
|
|
18
18
|
import type {
|
|
@@ -44,6 +44,7 @@ const NON_WIRE_KEYS = new Set<keyof SimpleStreamOptions>([
|
|
|
44
44
|
"cursorExecHandlers",
|
|
45
45
|
"cursorOnToolResult",
|
|
46
46
|
"providerSessionState",
|
|
47
|
+
"fallbackAttempt",
|
|
47
48
|
]);
|
|
48
49
|
|
|
49
50
|
function buildWireOptions(options: SimpleStreamOptions | undefined): Record<string, unknown> {
|
|
@@ -70,15 +71,27 @@ async function decodeGatewayError(response: Response): Promise<Error> {
|
|
|
70
71
|
if (typeof err === "object" && err !== null) {
|
|
71
72
|
const message = (err as { message?: unknown }).message;
|
|
72
73
|
const type = (err as { type?: unknown }).type;
|
|
74
|
+
const code = (err as { code?: unknown }).code;
|
|
73
75
|
const out = new Error(typeof message === "string" ? message : `auth-gateway ${status}`);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
const transportError = out as Error & {
|
|
77
|
+
status?: number;
|
|
78
|
+
type?: string;
|
|
79
|
+
providerCode?: string;
|
|
80
|
+
headers?: Headers;
|
|
81
|
+
};
|
|
82
|
+
transportError.status = status;
|
|
83
|
+
transportError.headers = response.headers;
|
|
84
|
+
if (typeof type === "string") transportError.type = type;
|
|
85
|
+
if (typeof code === "string") transportError.providerCode = code;
|
|
86
|
+
else if (typeof type === "string") transportError.providerCode = type;
|
|
76
87
|
return out;
|
|
77
88
|
}
|
|
78
89
|
}
|
|
79
90
|
const text = typeof body === "string" ? body : JSON.stringify(body);
|
|
80
91
|
const err = new Error(`auth-gateway ${status}: ${text || response.statusText}`);
|
|
81
|
-
|
|
92
|
+
const transportError = err as Error & { status?: number; headers?: Headers };
|
|
93
|
+
transportError.status = status;
|
|
94
|
+
transportError.headers = response.headers;
|
|
82
95
|
return err;
|
|
83
96
|
}
|
|
84
97
|
|
|
@@ -180,19 +193,18 @@ export function streamPiNative<TApi extends Api>(
|
|
|
180
193
|
}
|
|
181
194
|
|
|
182
195
|
if (!sawTerminal) {
|
|
183
|
-
// SSE closed before a terminal event reached us — synthesize one
|
|
184
|
-
// so awaiters of `.result()` resolve instead of hanging forever.
|
|
185
|
-
// Matches the gateway's own defensive fallback in
|
|
186
|
-
// `pi-native-server.encodeStream`.
|
|
187
196
|
const aborted = signal?.aborted === true;
|
|
188
|
-
const partial = makeSyntheticAssistant(model as Model<Api>);
|
|
189
197
|
if (aborted) {
|
|
198
|
+
const partial = makeSyntheticAssistant(model as Model<Api>);
|
|
190
199
|
partial.stopReason = "aborted";
|
|
191
200
|
partial.errorMessage = "stream closed without terminal event";
|
|
192
201
|
stream.push({ type: "error", reason: "aborted", error: partial });
|
|
193
202
|
} else {
|
|
194
|
-
|
|
195
|
-
|
|
203
|
+
const error = Object.assign(new Error("pi-native SSE stream closed without terminal event"), {
|
|
204
|
+
status: 502,
|
|
205
|
+
headers: response.headers,
|
|
206
|
+
});
|
|
207
|
+
stream.fail(error);
|
|
196
208
|
}
|
|
197
209
|
}
|
|
198
210
|
stream.end();
|