gsd-pi 2.68.1-dev.362687a → 2.68.1-dev.abc8f2b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/extensions/gsd/auto-model-selection.js +27 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -0
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -5
- package/dist/resources/extensions/gsd/guided-flow.js +25 -70
- package/dist/resources/extensions/gsd/model-router.js +85 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/dist/resources/extensions/gsd/templates/context.md +34 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/index.d.ts +3 -0
- package/packages/pi-ai/dist/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/index.js +2 -0
- package/packages/pi-ai/dist/index.js.map +1 -1
- package/packages/pi-ai/dist/providers/amazon-bedrock.js +2 -2
- package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.js +2 -2
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-shared.js +2 -2
- package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +2 -2
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +2 -2
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses-shared.js +2 -2
- package/packages/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
- package/packages/pi-ai/dist/providers/provider-capabilities.d.ts +59 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.js +173 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.js.map +1 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.test.js +132 -0
- package/packages/pi-ai/dist/providers/provider-capabilities.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/transform-messages-report.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/transform-messages-report.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/transform-messages-report.test.js +172 -0
- package/packages/pi-ai/dist/providers/transform-messages-report.test.js.map +1 -0
- package/packages/pi-ai/dist/providers/transform-messages.d.ts +34 -1
- package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/transform-messages.js +73 -2
- package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
- package/packages/pi-ai/src/index.ts +3 -0
- package/packages/pi-ai/src/providers/amazon-bedrock.ts +2 -2
- package/packages/pi-ai/src/providers/anthropic-shared.ts +2 -2
- package/packages/pi-ai/src/providers/google-shared.ts +2 -2
- package/packages/pi-ai/src/providers/mistral.ts +2 -2
- package/packages/pi-ai/src/providers/openai-completions.ts +2 -2
- package/packages/pi-ai/src/providers/openai-responses-shared.ts +2 -2
- package/packages/pi-ai/src/providers/provider-capabilities.test.ts +174 -0
- package/packages/pi-ai/src/providers/provider-capabilities.ts +215 -0
- package/packages/pi-ai/src/providers/transform-messages-report.test.ts +189 -0
- package/packages/pi-ai/src/providers/transform-messages.ts +94 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +10 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +15 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +41 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.d.ts +27 -0
- package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.js +69 -0
- package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +2 -2
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +3 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/index.ts +4 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +11 -1
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +18 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +45 -0
- package/packages/pi-coding-agent/src/core/tools/index.ts +7 -0
- package/packages/pi-coding-agent/src/core/tools/tool-compatibility-registry.ts +83 -0
- package/packages/pi-coding-agent/src/index.ts +9 -0
- package/src/resources/extensions/gsd/auto-model-selection.ts +36 -4
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -5
- package/src/resources/extensions/gsd/guided-flow.ts +22 -84
- package/src/resources/extensions/gsd/model-router.ts +117 -10
- package/src/resources/extensions/gsd/preferences-types.ts +3 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
- package/src/resources/extensions/gsd/templates/context.md +34 -2
- package/src/resources/extensions/gsd/tests/capability-router.test.ts +31 -7
- package/src/resources/extensions/gsd/tests/model-router.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +199 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +13 -16
- package/dist/resources/extensions/gsd/prompt-validation.js +0 -67
- package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +0 -424
- package/dist/resources/extensions/gsd/templates/context-enhanced.md +0 -138
- package/src/resources/extensions/gsd/prompt-validation.ts +0 -88
- package/src/resources/extensions/gsd/prompts/discuss-prepared.md +0 -424
- package/src/resources/extensions/gsd/templates/context-enhanced.md +0 -138
- package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +0 -223
- package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +0 -53
- package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +0 -525
- package/src/resources/extensions/gsd/tests/preparation.test.ts +0 -1211
- package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +0 -669
- /package/dist/web/standalone/.next/static/{VkiZZ5UjK7EfSjrWWd5RC → 3HMOXcBoys84RYd2F8a79}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{VkiZZ5UjK7EfSjrWWd5RC → 3HMOXcBoys84RYd2F8a79}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// GSD-2 — ProviderSwitchReport Tests (ADR-005 Phase 3)
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import assert from "node:assert/strict";
|
|
4
|
+
import { transformMessages, createEmptyReport, hasTransformations } from "./transform-messages.js";
|
|
5
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
6
|
+
function makeModel(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
id: "claude-sonnet-4-6",
|
|
9
|
+
name: "Claude Sonnet 4.6",
|
|
10
|
+
api: "anthropic-messages",
|
|
11
|
+
provider: "anthropic",
|
|
12
|
+
baseUrl: "",
|
|
13
|
+
reasoning: false,
|
|
14
|
+
input: ["text", "image"],
|
|
15
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
16
|
+
contextWindow: 200000,
|
|
17
|
+
maxTokens: 8192,
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function makeAssistantMsg(overrides = {}) {
|
|
22
|
+
return {
|
|
23
|
+
role: "assistant",
|
|
24
|
+
content: [],
|
|
25
|
+
api: "anthropic-messages",
|
|
26
|
+
provider: "anthropic",
|
|
27
|
+
model: "claude-sonnet-4-6",
|
|
28
|
+
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
|
|
29
|
+
stopReason: "stop",
|
|
30
|
+
timestamp: Date.now(),
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// ─── createEmptyReport / hasTransformations ─────────────────────────────────
|
|
35
|
+
describe("createEmptyReport", () => {
|
|
36
|
+
test("creates report with zero counters", () => {
|
|
37
|
+
const report = createEmptyReport("anthropic-messages", "openai-responses");
|
|
38
|
+
assert.equal(report.fromApi, "anthropic-messages");
|
|
39
|
+
assert.equal(report.toApi, "openai-responses");
|
|
40
|
+
assert.equal(report.thinkingBlocksDropped, 0);
|
|
41
|
+
assert.equal(report.thinkingBlocksDowngraded, 0);
|
|
42
|
+
assert.equal(report.toolCallIdsRemapped, 0);
|
|
43
|
+
assert.equal(report.syntheticToolResultsInserted, 0);
|
|
44
|
+
assert.equal(report.thoughtSignaturesDropped, 0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("hasTransformations", () => {
|
|
48
|
+
test("returns false for empty report", () => {
|
|
49
|
+
const report = createEmptyReport("a", "b");
|
|
50
|
+
assert.equal(hasTransformations(report), false);
|
|
51
|
+
});
|
|
52
|
+
test("returns true when any counter is non-zero", () => {
|
|
53
|
+
const report = createEmptyReport("a", "b");
|
|
54
|
+
report.thinkingBlocksDropped = 1;
|
|
55
|
+
assert.equal(hasTransformations(report), true);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// ─── Report Tracking in transformMessages ───────────────────────────────────
|
|
59
|
+
describe("transformMessages with report tracking", () => {
|
|
60
|
+
test("tracks thinking blocks dropped for redacted cross-model", () => {
|
|
61
|
+
const model = makeModel({ id: "gpt-5", api: "openai-responses", provider: "openai" });
|
|
62
|
+
const messages = [
|
|
63
|
+
makeAssistantMsg({
|
|
64
|
+
content: [
|
|
65
|
+
{ type: "thinking", thinking: "", redacted: true },
|
|
66
|
+
{ type: "text", text: "Hello" },
|
|
67
|
+
],
|
|
68
|
+
}),
|
|
69
|
+
];
|
|
70
|
+
const report = createEmptyReport("anthropic-messages", "openai-responses");
|
|
71
|
+
transformMessages(messages, model, undefined, report);
|
|
72
|
+
assert.equal(report.thinkingBlocksDropped, 1);
|
|
73
|
+
});
|
|
74
|
+
test("tracks thinking blocks downgraded to plain text", () => {
|
|
75
|
+
const model = makeModel({ id: "gpt-5", api: "openai-responses", provider: "openai" });
|
|
76
|
+
const messages = [
|
|
77
|
+
makeAssistantMsg({
|
|
78
|
+
content: [
|
|
79
|
+
{ type: "thinking", thinking: "Let me think about this..." },
|
|
80
|
+
{ type: "text", text: "Here is my answer" },
|
|
81
|
+
],
|
|
82
|
+
}),
|
|
83
|
+
];
|
|
84
|
+
const report = createEmptyReport("anthropic-messages", "openai-responses");
|
|
85
|
+
transformMessages(messages, model, undefined, report);
|
|
86
|
+
assert.equal(report.thinkingBlocksDowngraded, 1);
|
|
87
|
+
});
|
|
88
|
+
test("tracks tool call IDs remapped", () => {
|
|
89
|
+
const model = makeModel({ id: "claude-sonnet-4-6", api: "anthropic-messages", provider: "anthropic" });
|
|
90
|
+
const toolCall = {
|
|
91
|
+
type: "toolCall",
|
|
92
|
+
id: "original-long-id-that-needs-normalization|with-special-chars",
|
|
93
|
+
name: "bash",
|
|
94
|
+
arguments: { command: "ls" },
|
|
95
|
+
};
|
|
96
|
+
const messages = [
|
|
97
|
+
makeAssistantMsg({
|
|
98
|
+
provider: "openai",
|
|
99
|
+
api: "openai-responses",
|
|
100
|
+
model: "gpt-5",
|
|
101
|
+
content: [toolCall],
|
|
102
|
+
}),
|
|
103
|
+
];
|
|
104
|
+
const normalizer = (id) => id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
105
|
+
const report = createEmptyReport("openai-responses", "anthropic-messages");
|
|
106
|
+
transformMessages(messages, model, normalizer, report);
|
|
107
|
+
assert.equal(report.toolCallIdsRemapped, 1);
|
|
108
|
+
});
|
|
109
|
+
test("tracks thought signatures dropped", () => {
|
|
110
|
+
const model = makeModel({ id: "claude-sonnet-4-6", api: "anthropic-messages", provider: "anthropic" });
|
|
111
|
+
const toolCall = {
|
|
112
|
+
type: "toolCall",
|
|
113
|
+
id: "tc_001",
|
|
114
|
+
name: "bash",
|
|
115
|
+
arguments: { command: "ls" },
|
|
116
|
+
thoughtSignature: "some-opaque-signature",
|
|
117
|
+
};
|
|
118
|
+
const messages = [
|
|
119
|
+
makeAssistantMsg({
|
|
120
|
+
provider: "google",
|
|
121
|
+
api: "google-generative-ai",
|
|
122
|
+
model: "gemini-2.5-pro",
|
|
123
|
+
content: [toolCall],
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
126
|
+
const report = createEmptyReport("google-generative-ai", "anthropic-messages");
|
|
127
|
+
transformMessages(messages, model, undefined, report);
|
|
128
|
+
assert.equal(report.thoughtSignaturesDropped, 1);
|
|
129
|
+
});
|
|
130
|
+
test("tracks synthetic tool results inserted", () => {
|
|
131
|
+
const model = makeModel();
|
|
132
|
+
const toolCall = {
|
|
133
|
+
type: "toolCall",
|
|
134
|
+
id: "tc_orphan",
|
|
135
|
+
name: "bash",
|
|
136
|
+
arguments: { command: "ls" },
|
|
137
|
+
};
|
|
138
|
+
// Assistant message with tool call followed by another assistant (no tool result)
|
|
139
|
+
const messages = [
|
|
140
|
+
makeAssistantMsg({ content: [toolCall, { type: "text", text: "Using bash" }] }),
|
|
141
|
+
makeAssistantMsg({ content: [{ type: "text", text: "Next message" }] }),
|
|
142
|
+
];
|
|
143
|
+
const report = createEmptyReport("anthropic-messages", "anthropic-messages");
|
|
144
|
+
transformMessages(messages, model, undefined, report);
|
|
145
|
+
assert.equal(report.syntheticToolResultsInserted, 1);
|
|
146
|
+
});
|
|
147
|
+
test("does not count transformations for same-model messages", () => {
|
|
148
|
+
const model = makeModel();
|
|
149
|
+
const messages = [
|
|
150
|
+
makeAssistantMsg({
|
|
151
|
+
content: [
|
|
152
|
+
{ type: "thinking", thinking: "Let me think..." },
|
|
153
|
+
{ type: "text", text: "Answer" },
|
|
154
|
+
],
|
|
155
|
+
}),
|
|
156
|
+
];
|
|
157
|
+
const report = createEmptyReport("anthropic-messages", "anthropic-messages");
|
|
158
|
+
transformMessages(messages, model, undefined, report);
|
|
159
|
+
assert.equal(report.thinkingBlocksDowngraded, 0);
|
|
160
|
+
assert.equal(report.thinkingBlocksDropped, 0);
|
|
161
|
+
});
|
|
162
|
+
test("works without report parameter (backward compatible)", () => {
|
|
163
|
+
const model = makeModel();
|
|
164
|
+
const messages = [
|
|
165
|
+
makeAssistantMsg({ content: [{ type: "text", text: "Hello" }] }),
|
|
166
|
+
];
|
|
167
|
+
// Should not throw
|
|
168
|
+
const result = transformMessages(messages, model);
|
|
169
|
+
assert.ok(Array.isArray(result));
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
//# sourceMappingURL=transform-messages-report.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transform-messages-report.test.js","sourceRoot":"","sources":["../../src/providers/transform-messages-report.test.ts"],"names":[],"mappings":"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","sourcesContent":["// GSD-2 — ProviderSwitchReport Tests (ADR-005 Phase 3)\nimport { describe, test } from \"node:test\";\nimport assert from \"node:assert/strict\";\n\nimport { transformMessages, createEmptyReport, hasTransformations } from \"./transform-messages.js\";\nimport type { ProviderSwitchReport } from \"./transform-messages.js\";\nimport type { Message, Model, AssistantMessage, ToolCall } from \"../types.js\";\n\n// ─── Helpers ────────────────────────────────────────────────────────────────\n\nfunction makeModel(overrides: Partial<Model<any>> = {}): Model<any> {\n return {\n id: \"claude-sonnet-4-6\",\n name: \"Claude Sonnet 4.6\",\n api: \"anthropic-messages\",\n provider: \"anthropic\",\n baseUrl: \"\",\n reasoning: false,\n input: [\"text\", \"image\"],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 200000,\n maxTokens: 8192,\n ...overrides,\n } as Model<any>;\n}\n\nfunction makeAssistantMsg(overrides: Partial<AssistantMessage> = {}): AssistantMessage {\n return {\n role: \"assistant\",\n content: [],\n api: \"anthropic-messages\",\n provider: \"anthropic\",\n model: \"claude-sonnet-4-6\",\n usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },\n stopReason: \"stop\",\n timestamp: Date.now(),\n ...overrides,\n };\n}\n\n// ─── createEmptyReport / hasTransformations ─────────────────────────────────\n\ndescribe(\"createEmptyReport\", () => {\n test(\"creates report with zero counters\", () => {\n const report = createEmptyReport(\"anthropic-messages\", \"openai-responses\");\n assert.equal(report.fromApi, \"anthropic-messages\");\n assert.equal(report.toApi, \"openai-responses\");\n assert.equal(report.thinkingBlocksDropped, 0);\n assert.equal(report.thinkingBlocksDowngraded, 0);\n assert.equal(report.toolCallIdsRemapped, 0);\n assert.equal(report.syntheticToolResultsInserted, 0);\n assert.equal(report.thoughtSignaturesDropped, 0);\n });\n});\n\ndescribe(\"hasTransformations\", () => {\n test(\"returns false for empty report\", () => {\n const report = createEmptyReport(\"a\", \"b\");\n assert.equal(hasTransformations(report), false);\n });\n\n test(\"returns true when any counter is non-zero\", () => {\n const report = createEmptyReport(\"a\", \"b\");\n report.thinkingBlocksDropped = 1;\n assert.equal(hasTransformations(report), true);\n });\n});\n\n// ─── Report Tracking in transformMessages ───────────────────────────────────\n\ndescribe(\"transformMessages with report tracking\", () => {\n test(\"tracks thinking blocks dropped for redacted cross-model\", () => {\n const model = makeModel({ id: \"gpt-5\", api: \"openai-responses\", provider: \"openai\" });\n const messages: Message[] = [\n makeAssistantMsg({\n content: [\n { type: \"thinking\", thinking: \"\", redacted: true },\n { type: \"text\", text: \"Hello\" },\n ],\n }),\n ];\n const report = createEmptyReport(\"anthropic-messages\", \"openai-responses\");\n transformMessages(messages, model, undefined, report);\n assert.equal(report.thinkingBlocksDropped, 1);\n });\n\n test(\"tracks thinking blocks downgraded to plain text\", () => {\n const model = makeModel({ id: \"gpt-5\", api: \"openai-responses\", provider: \"openai\" });\n const messages: Message[] = [\n makeAssistantMsg({\n content: [\n { type: \"thinking\", thinking: \"Let me think about this...\" },\n { type: \"text\", text: \"Here is my answer\" },\n ],\n }),\n ];\n const report = createEmptyReport(\"anthropic-messages\", \"openai-responses\");\n transformMessages(messages, model, undefined, report);\n assert.equal(report.thinkingBlocksDowngraded, 1);\n });\n\n test(\"tracks tool call IDs remapped\", () => {\n const model = makeModel({ id: \"claude-sonnet-4-6\", api: \"anthropic-messages\", provider: \"anthropic\" });\n const toolCall: ToolCall = {\n type: \"toolCall\",\n id: \"original-long-id-that-needs-normalization|with-special-chars\",\n name: \"bash\",\n arguments: { command: \"ls\" },\n };\n const messages: Message[] = [\n makeAssistantMsg({\n provider: \"openai\",\n api: \"openai-responses\",\n model: \"gpt-5\",\n content: [toolCall],\n }),\n ];\n const normalizer = (id: string) => id.replace(/[^a-zA-Z0-9_-]/g, \"_\").slice(0, 64);\n const report = createEmptyReport(\"openai-responses\", \"anthropic-messages\");\n transformMessages(messages, model, normalizer, report);\n assert.equal(report.toolCallIdsRemapped, 1);\n });\n\n test(\"tracks thought signatures dropped\", () => {\n const model = makeModel({ id: \"claude-sonnet-4-6\", api: \"anthropic-messages\", provider: \"anthropic\" });\n const toolCall: ToolCall = {\n type: \"toolCall\",\n id: \"tc_001\",\n name: \"bash\",\n arguments: { command: \"ls\" },\n thoughtSignature: \"some-opaque-signature\",\n };\n const messages: Message[] = [\n makeAssistantMsg({\n provider: \"google\",\n api: \"google-generative-ai\",\n model: \"gemini-2.5-pro\",\n content: [toolCall],\n }),\n ];\n const report = createEmptyReport(\"google-generative-ai\", \"anthropic-messages\");\n transformMessages(messages, model, undefined, report);\n assert.equal(report.thoughtSignaturesDropped, 1);\n });\n\n test(\"tracks synthetic tool results inserted\", () => {\n const model = makeModel();\n const toolCall: ToolCall = {\n type: \"toolCall\",\n id: \"tc_orphan\",\n name: \"bash\",\n arguments: { command: \"ls\" },\n };\n // Assistant message with tool call followed by another assistant (no tool result)\n const messages: Message[] = [\n makeAssistantMsg({ content: [toolCall, { type: \"text\", text: \"Using bash\" }] }),\n makeAssistantMsg({ content: [{ type: \"text\", text: \"Next message\" }] }),\n ];\n const report = createEmptyReport(\"anthropic-messages\", \"anthropic-messages\");\n transformMessages(messages, model, undefined, report);\n assert.equal(report.syntheticToolResultsInserted, 1);\n });\n\n test(\"does not count transformations for same-model messages\", () => {\n const model = makeModel();\n const messages: Message[] = [\n makeAssistantMsg({\n content: [\n { type: \"thinking\", thinking: \"Let me think...\" },\n { type: \"text\", text: \"Answer\" },\n ],\n }),\n ];\n const report = createEmptyReport(\"anthropic-messages\", \"anthropic-messages\");\n transformMessages(messages, model, undefined, report);\n assert.equal(report.thinkingBlocksDowngraded, 0);\n assert.equal(report.thinkingBlocksDropped, 0);\n });\n\n test(\"works without report parameter (backward compatible)\", () => {\n const model = makeModel();\n const messages: Message[] = [\n makeAssistantMsg({ content: [{ type: \"text\", text: \"Hello\" }] }),\n ];\n // Should not throw\n const result = transformMessages(messages, model);\n assert.ok(Array.isArray(result));\n });\n});\n"]}
|
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
import type { Api, AssistantMessage, Message, Model } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Report of context transformations during a cross-provider switch (ADR-005 Phase 3).
|
|
4
|
+
* Tracks what was lost or downgraded when replaying conversation history to a different provider.
|
|
5
|
+
*/
|
|
6
|
+
export interface ProviderSwitchReport {
|
|
7
|
+
/** API of the messages being transformed from */
|
|
8
|
+
fromApi: string;
|
|
9
|
+
/** API of the target model */
|
|
10
|
+
toApi: string;
|
|
11
|
+
/** Number of thinking blocks completely dropped (redacted/encrypted, cross-model) */
|
|
12
|
+
thinkingBlocksDropped: number;
|
|
13
|
+
/** Number of thinking blocks downgraded from structured to plain text */
|
|
14
|
+
thinkingBlocksDowngraded: number;
|
|
15
|
+
/** Number of tool call IDs that were remapped/normalized */
|
|
16
|
+
toolCallIdsRemapped: number;
|
|
17
|
+
/** Number of synthetic tool results inserted for orphaned tool calls */
|
|
18
|
+
syntheticToolResultsInserted: number;
|
|
19
|
+
/** Number of thought signatures dropped (Google-specific opaque context) */
|
|
20
|
+
thoughtSignaturesDropped: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create an empty provider switch report.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createEmptyReport(fromApi: string, toApi: string): ProviderSwitchReport;
|
|
26
|
+
/**
|
|
27
|
+
* Check if a provider switch report has any non-zero transformations.
|
|
28
|
+
*/
|
|
29
|
+
export declare function hasTransformations(report: ProviderSwitchReport): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Create a report, run transformMessages, and log if non-empty.
|
|
32
|
+
* Convenience wrapper for provider adapters (ADR-005).
|
|
33
|
+
*/
|
|
34
|
+
export declare function transformMessagesWithReport<TApi extends Api>(messages: Message[], model: Model<TApi>, normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string, sourceApi?: string): Message[];
|
|
2
35
|
/**
|
|
3
36
|
* Normalize tool call ID for cross-provider compatibility.
|
|
4
37
|
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
|
5
38
|
* Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
|
|
6
39
|
*/
|
|
7
|
-
export declare function transformMessages<TApi extends Api>(messages: Message[], model: Model<TApi>, normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string): Message[];
|
|
40
|
+
export declare function transformMessages<TApi extends Api>(messages: Message[], model: Model<TApi>, normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string, report?: ProviderSwitchReport): Message[];
|
|
8
41
|
//# sourceMappingURL=transform-messages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-messages.d.ts","sourceRoot":"","sources":["../../src/providers/transform-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAA+B,MAAM,aAAa,CAAC;AAEtG;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,GAAG,EACjD,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,mBAAmB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,MAAM,
|
|
1
|
+
{"version":3,"file":"transform-messages.d.ts","sourceRoot":"","sources":["../../src/providers/transform-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAA+B,MAAM,aAAa,CAAC;AAEtG;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACpC,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,qBAAqB,EAAE,MAAM,CAAC;IAC9B,yEAAyE;IACzE,wBAAwB,EAAE,MAAM,CAAC;IACjC,4DAA4D;IAC5D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,4BAA4B,EAAE,MAAM,CAAC;IACrC,4EAA4E;IAC5E,wBAAwB,EAAE,MAAM,CAAC;CACjC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAUtF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAQxE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,SAAS,GAAG,EAC3D,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,mBAAmB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,MAAM,EAC1F,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,EAAE,CAOX;AAiBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,GAAG,EACjD,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,mBAAmB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,MAAM,EAC1F,MAAM,CAAC,EAAE,oBAAoB,GAC3B,OAAO,EAAE,CA0KX"}
|
|
@@ -1,9 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an empty provider switch report.
|
|
3
|
+
*/
|
|
4
|
+
export function createEmptyReport(fromApi, toApi) {
|
|
5
|
+
return {
|
|
6
|
+
fromApi,
|
|
7
|
+
toApi,
|
|
8
|
+
thinkingBlocksDropped: 0,
|
|
9
|
+
thinkingBlocksDowngraded: 0,
|
|
10
|
+
toolCallIdsRemapped: 0,
|
|
11
|
+
syntheticToolResultsInserted: 0,
|
|
12
|
+
thoughtSignaturesDropped: 0,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Check if a provider switch report has any non-zero transformations.
|
|
17
|
+
*/
|
|
18
|
+
export function hasTransformations(report) {
|
|
19
|
+
return (report.thinkingBlocksDropped > 0 ||
|
|
20
|
+
report.thinkingBlocksDowngraded > 0 ||
|
|
21
|
+
report.toolCallIdsRemapped > 0 ||
|
|
22
|
+
report.syntheticToolResultsInserted > 0 ||
|
|
23
|
+
report.thoughtSignaturesDropped > 0);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a report, run transformMessages, and log if non-empty.
|
|
27
|
+
* Convenience wrapper for provider adapters (ADR-005).
|
|
28
|
+
*/
|
|
29
|
+
export function transformMessagesWithReport(messages, model, normalizeToolCallId, sourceApi) {
|
|
30
|
+
const report = createEmptyReport(sourceApi ?? "unknown", model.api);
|
|
31
|
+
const result = transformMessages(messages, model, normalizeToolCallId, report);
|
|
32
|
+
if (hasTransformations(report)) {
|
|
33
|
+
logProviderSwitchReport(report);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
/** Log a non-empty ProviderSwitchReport as a debug-level warning. */
|
|
38
|
+
function logProviderSwitchReport(report) {
|
|
39
|
+
const parts = [`Provider switch ${report.fromApi} → ${report.toApi}:`];
|
|
40
|
+
if (report.thinkingBlocksDropped > 0)
|
|
41
|
+
parts.push(`${report.thinkingBlocksDropped} thinking blocks dropped`);
|
|
42
|
+
if (report.thinkingBlocksDowngraded > 0)
|
|
43
|
+
parts.push(`${report.thinkingBlocksDowngraded} thinking blocks downgraded`);
|
|
44
|
+
if (report.toolCallIdsRemapped > 0)
|
|
45
|
+
parts.push(`${report.toolCallIdsRemapped} tool call IDs remapped`);
|
|
46
|
+
if (report.syntheticToolResultsInserted > 0)
|
|
47
|
+
parts.push(`${report.syntheticToolResultsInserted} synthetic tool results inserted`);
|
|
48
|
+
if (report.thoughtSignaturesDropped > 0)
|
|
49
|
+
parts.push(`${report.thoughtSignaturesDropped} thought signatures dropped`);
|
|
50
|
+
// Use process.stderr for debug output — this is observable in verbose/debug modes
|
|
51
|
+
// without polluting stdout which may be used for structured output (RPC/MCP).
|
|
52
|
+
if (process.env.GSD_VERBOSE === "1" || process.env.PI_VERBOSE === "1") {
|
|
53
|
+
process.stderr.write(`[provider-switch] ${parts.join(", ")}\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
1
56
|
/**
|
|
2
57
|
* Normalize tool call ID for cross-provider compatibility.
|
|
3
58
|
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
|
4
59
|
* Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
|
|
5
60
|
*/
|
|
6
|
-
export function transformMessages(messages, model, normalizeToolCallId) {
|
|
61
|
+
export function transformMessages(messages, model, normalizeToolCallId, report) {
|
|
7
62
|
// Build a map of original tool call IDs to normalized IDs
|
|
8
63
|
const toolCallIdMap = new Map();
|
|
9
64
|
// First pass: transform messages (thinking blocks, tool call ID normalization)
|
|
@@ -31,6 +86,8 @@ export function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
31
86
|
// Redacted thinking is opaque encrypted content, only valid for the same model.
|
|
32
87
|
// Drop it for cross-model to avoid API errors.
|
|
33
88
|
if (block.redacted) {
|
|
89
|
+
if (!isSameModel && report)
|
|
90
|
+
report.thinkingBlocksDropped++;
|
|
34
91
|
return isSameModel ? block : [];
|
|
35
92
|
}
|
|
36
93
|
// For same model: keep thinking blocks with signatures (needed for replay)
|
|
@@ -38,10 +95,16 @@ export function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
38
95
|
if (isSameModel && block.thinkingSignature)
|
|
39
96
|
return block;
|
|
40
97
|
// Skip empty thinking blocks, convert others to plain text
|
|
41
|
-
if (!block.thinking || block.thinking.trim() === "")
|
|
98
|
+
if (!block.thinking || block.thinking.trim() === "") {
|
|
99
|
+
if (!isSameModel && report)
|
|
100
|
+
report.thinkingBlocksDropped++;
|
|
42
101
|
return [];
|
|
102
|
+
}
|
|
43
103
|
if (isSameModel)
|
|
44
104
|
return block;
|
|
105
|
+
// Downgrade: structured thinking → plain text
|
|
106
|
+
if (report)
|
|
107
|
+
report.thinkingBlocksDowngraded++;
|
|
45
108
|
return {
|
|
46
109
|
type: "text",
|
|
47
110
|
text: block.thinking,
|
|
@@ -61,12 +124,16 @@ export function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
61
124
|
if (!isSameModel && toolCall.thoughtSignature) {
|
|
62
125
|
normalizedToolCall = { ...toolCall };
|
|
63
126
|
delete normalizedToolCall.thoughtSignature;
|
|
127
|
+
if (report)
|
|
128
|
+
report.thoughtSignaturesDropped++;
|
|
64
129
|
}
|
|
65
130
|
if (!isSameModel && normalizeToolCallId) {
|
|
66
131
|
const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
|
|
67
132
|
if (normalizedId !== toolCall.id) {
|
|
68
133
|
toolCallIdMap.set(toolCall.id, normalizedId);
|
|
69
134
|
normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
|
|
135
|
+
if (report)
|
|
136
|
+
report.toolCallIdsRemapped++;
|
|
70
137
|
}
|
|
71
138
|
}
|
|
72
139
|
return normalizedToolCall;
|
|
@@ -100,6 +167,8 @@ export function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
100
167
|
isError: true,
|
|
101
168
|
timestamp: Date.now(),
|
|
102
169
|
});
|
|
170
|
+
if (report)
|
|
171
|
+
report.syntheticToolResultsInserted++;
|
|
103
172
|
}
|
|
104
173
|
}
|
|
105
174
|
pendingToolCalls = [];
|
|
@@ -139,6 +208,8 @@ export function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
139
208
|
isError: true,
|
|
140
209
|
timestamp: Date.now(),
|
|
141
210
|
});
|
|
211
|
+
if (report)
|
|
212
|
+
report.syntheticToolResultsInserted++;
|
|
142
213
|
}
|
|
143
214
|
}
|
|
144
215
|
pendingToolCalls = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-messages.js","sourceRoot":"","sources":["../../src/providers/transform-messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from \"../types.js\";\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\n\t// First pass: transform messages (thinking blocks, tool call ID normalization)\n\tconst transformed = messages.map((msg) => {\n\t\t// User messages pass through unchanged\n\t\tif (msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block) => {\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\t// Redacted thinking is opaque encrypted content, only valid for the same model.\n\t\t\t\t\t// Drop it for cross-model to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\treturn isSameModel ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// For same model: keep thinking blocks with signatures (needed for replay)\n\t\t\t\t\t// even if the thinking text is empty (OpenAI encrypted reasoning)\n\t\t\t\t\tif (isSameModel && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") return [];\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\n\tfor (let i = 0; i < transformed.length; i++) {\n\t\tconst msg = transformed[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// User message interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"transform-messages.js","sourceRoot":"","sources":["../../src/providers/transform-messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from \"../types.js\";\n\n/**\n * Report of context transformations during a cross-provider switch (ADR-005 Phase 3).\n * Tracks what was lost or downgraded when replaying conversation history to a different provider.\n */\nexport interface ProviderSwitchReport {\n\t/** API of the messages being transformed from */\n\tfromApi: string;\n\t/** API of the target model */\n\ttoApi: string;\n\t/** Number of thinking blocks completely dropped (redacted/encrypted, cross-model) */\n\tthinkingBlocksDropped: number;\n\t/** Number of thinking blocks downgraded from structured to plain text */\n\tthinkingBlocksDowngraded: number;\n\t/** Number of tool call IDs that were remapped/normalized */\n\ttoolCallIdsRemapped: number;\n\t/** Number of synthetic tool results inserted for orphaned tool calls */\n\tsyntheticToolResultsInserted: number;\n\t/** Number of thought signatures dropped (Google-specific opaque context) */\n\tthoughtSignaturesDropped: number;\n}\n\n/**\n * Create an empty provider switch report.\n */\nexport function createEmptyReport(fromApi: string, toApi: string): ProviderSwitchReport {\n\treturn {\n\t\tfromApi,\n\t\ttoApi,\n\t\tthinkingBlocksDropped: 0,\n\t\tthinkingBlocksDowngraded: 0,\n\t\ttoolCallIdsRemapped: 0,\n\t\tsyntheticToolResultsInserted: 0,\n\t\tthoughtSignaturesDropped: 0,\n\t};\n}\n\n/**\n * Check if a provider switch report has any non-zero transformations.\n */\nexport function hasTransformations(report: ProviderSwitchReport): boolean {\n\treturn (\n\t\treport.thinkingBlocksDropped > 0 ||\n\t\treport.thinkingBlocksDowngraded > 0 ||\n\t\treport.toolCallIdsRemapped > 0 ||\n\t\treport.syntheticToolResultsInserted > 0 ||\n\t\treport.thoughtSignaturesDropped > 0\n\t);\n}\n\n/**\n * Create a report, run transformMessages, and log if non-empty.\n * Convenience wrapper for provider adapters (ADR-005).\n */\nexport function transformMessagesWithReport<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n\tsourceApi?: string,\n): Message[] {\n\tconst report = createEmptyReport(sourceApi ?? \"unknown\", model.api);\n\tconst result = transformMessages(messages, model, normalizeToolCallId, report);\n\tif (hasTransformations(report)) {\n\t\tlogProviderSwitchReport(report);\n\t}\n\treturn result;\n}\n\n/** Log a non-empty ProviderSwitchReport as a debug-level warning. */\nfunction logProviderSwitchReport(report: ProviderSwitchReport): void {\n\tconst parts: string[] = [`Provider switch ${report.fromApi} → ${report.toApi}:`];\n\tif (report.thinkingBlocksDropped > 0) parts.push(`${report.thinkingBlocksDropped} thinking blocks dropped`);\n\tif (report.thinkingBlocksDowngraded > 0) parts.push(`${report.thinkingBlocksDowngraded} thinking blocks downgraded`);\n\tif (report.toolCallIdsRemapped > 0) parts.push(`${report.toolCallIdsRemapped} tool call IDs remapped`);\n\tif (report.syntheticToolResultsInserted > 0) parts.push(`${report.syntheticToolResultsInserted} synthetic tool results inserted`);\n\tif (report.thoughtSignaturesDropped > 0) parts.push(`${report.thoughtSignaturesDropped} thought signatures dropped`);\n\t// Use process.stderr for debug output — this is observable in verbose/debug modes\n\t// without polluting stdout which may be used for structured output (RPC/MCP).\n\tif (process.env.GSD_VERBOSE === \"1\" || process.env.PI_VERBOSE === \"1\") {\n\t\tprocess.stderr.write(`[provider-switch] ${parts.join(\", \")}\\n`);\n\t}\n}\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n\treport?: ProviderSwitchReport,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\n\t// First pass: transform messages (thinking blocks, tool call ID normalization)\n\tconst transformed = messages.map((msg) => {\n\t\t// User messages pass through unchanged\n\t\tif (msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block) => {\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\t// Redacted thinking is opaque encrypted content, only valid for the same model.\n\t\t\t\t\t// Drop it for cross-model to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\tif (!isSameModel && report) report.thinkingBlocksDropped++;\n\t\t\t\t\t\treturn isSameModel ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// For same model: keep thinking blocks with signatures (needed for replay)\n\t\t\t\t\t// even if the thinking text is empty (OpenAI encrypted reasoning)\n\t\t\t\t\tif (isSameModel && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") {\n\t\t\t\t\t\tif (!isSameModel && report) report.thinkingBlocksDropped++;\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\t// Downgrade: structured thinking → plain text\n\t\t\t\t\tif (report) report.thinkingBlocksDowngraded++;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t\tif (report) report.thoughtSignaturesDropped++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t\tif (report) report.toolCallIdsRemapped++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\n\tfor (let i = 0; i < transformed.length; i++) {\n\t\tconst msg = transformed[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t\tif (report) report.syntheticToolResultsInserted++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// User message interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t\tif (report) report.syntheticToolResultsInserted++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
|
|
@@ -12,7 +12,10 @@ export * from "./providers/google-vertex.js";
|
|
|
12
12
|
export * from "./providers/mistral.js";
|
|
13
13
|
export * from "./providers/openai-completions.js";
|
|
14
14
|
export * from "./providers/openai-responses.js";
|
|
15
|
+
export * from "./providers/provider-capabilities.js";
|
|
15
16
|
export * from "./providers/register-builtins.js";
|
|
17
|
+
export type { ProviderSwitchReport } from "./providers/transform-messages.js";
|
|
18
|
+
export { createEmptyReport, hasTransformations, transformMessagesWithReport } from "./providers/transform-messages.js";
|
|
16
19
|
export * from "./stream.js";
|
|
17
20
|
export * from "./types.js";
|
|
18
21
|
export * from "./utils/event-stream.js";
|
|
@@ -43,7 +43,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
|
43
43
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
44
44
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
45
45
|
import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.js";
|
|
46
|
-
import {
|
|
46
|
+
import { transformMessagesWithReport } from "./transform-messages.js";
|
|
47
47
|
|
|
48
48
|
export interface BedrockOptions extends StreamOptions {
|
|
49
49
|
region?: string;
|
|
@@ -487,7 +487,7 @@ function convertMessages(
|
|
|
487
487
|
cacheRetention: CacheRetention,
|
|
488
488
|
): Message[] {
|
|
489
489
|
const result: Message[] = [];
|
|
490
|
-
const transformedMessages =
|
|
490
|
+
const transformedMessages = transformMessagesWithReport(context.messages, model, normalizeToolCallId, "bedrock-converse-stream");
|
|
491
491
|
|
|
492
492
|
for (let i = 0; i < transformedMessages.length; i++) {
|
|
493
493
|
const m = transformedMessages[i];
|
|
@@ -33,7 +33,7 @@ import type { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
|
33
33
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
34
34
|
import { hasXmlParameterTags, repairToolJson } from "../utils/repair-tool-json.js";
|
|
35
35
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
36
|
-
import {
|
|
36
|
+
import { transformMessagesWithReport } from "./transform-messages.js";
|
|
37
37
|
|
|
38
38
|
export type AnthropicEffort = "low" | "medium" | "high" | "max";
|
|
39
39
|
|
|
@@ -235,7 +235,7 @@ export function convertMessages(
|
|
|
235
235
|
): MessageParam[] {
|
|
236
236
|
const params: MessageParam[] = [];
|
|
237
237
|
|
|
238
|
-
const transformedMessages =
|
|
238
|
+
const transformedMessages = transformMessagesWithReport(messages, model, normalizeToolCallId, "anthropic-messages");
|
|
239
239
|
|
|
240
240
|
for (let i = 0; i < transformedMessages.length; i++) {
|
|
241
241
|
const msg = transformedMessages[i];
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { type Content, FinishReason, FunctionCallingConfigMode, type Part } from "@google/genai";
|
|
6
6
|
import type { Context, ImageContent, Model, StopReason, TextContent, Tool } from "../types.js";
|
|
7
7
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
8
|
-
import {
|
|
8
|
+
import { transformMessagesWithReport } from "./transform-messages.js";
|
|
9
9
|
|
|
10
10
|
type GoogleApiType = "google-generative-ai" | "google-gemini-cli" | "google-vertex";
|
|
11
11
|
|
|
@@ -80,7 +80,7 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|
|
80
80
|
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
const transformedMessages =
|
|
83
|
+
const transformedMessages = transformMessagesWithReport(context.messages, model, normalizeToolCallId, "google-generative-ai");
|
|
84
84
|
|
|
85
85
|
for (const msg of transformedMessages) {
|
|
86
86
|
if (msg.role === "user") {
|
|
@@ -39,7 +39,7 @@ import { shortHash } from "../utils/hash.js";
|
|
|
39
39
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
40
40
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
41
41
|
import { buildBaseOptions, clampReasoning } from "./simple-options.js";
|
|
42
|
-
import {
|
|
42
|
+
import { transformMessagesWithReport } from "./transform-messages.js";
|
|
43
43
|
|
|
44
44
|
const MISTRAL_TOOL_CALL_ID_LENGTH = 9;
|
|
45
45
|
const MAX_MISTRAL_ERROR_BODY_CHARS = 4000;
|
|
@@ -79,7 +79,7 @@ export const streamMistral: StreamFunction<"mistral-conversations", MistralOptio
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
const normalizeMistralToolCallId = createMistralToolCallIdNormalizer();
|
|
82
|
-
const transformedMessages =
|
|
82
|
+
const transformedMessages = transformMessagesWithReport(context.messages, model, (id) => normalizeMistralToolCallId(id), "mistral-conversations");
|
|
83
83
|
|
|
84
84
|
let payload = buildChatPayload(model, context, transformedMessages, options);
|
|
85
85
|
const nextPayload = await options?.onPayload?.(payload, model);
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
finalizeStream,
|
|
40
40
|
handleStreamError,
|
|
41
41
|
} from "./openai-shared.js";
|
|
42
|
-
import {
|
|
42
|
+
import { transformMessagesWithReport } from "./transform-messages.js";
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Check if conversation messages contain tool calls or tool results.
|
|
@@ -441,7 +441,7 @@ export function convertMessages(
|
|
|
441
441
|
return id;
|
|
442
442
|
};
|
|
443
443
|
|
|
444
|
-
const transformedMessages =
|
|
444
|
+
const transformedMessages = transformMessagesWithReport(context.messages, model, (id) => normalizeToolCallId(id), "openai-completions");
|
|
445
445
|
|
|
446
446
|
if (context.systemPrompt) {
|
|
447
447
|
const useDeveloperRole = model.reasoning && compat.supportsDeveloperRole;
|
|
@@ -30,7 +30,7 @@ import type { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
|
30
30
|
import { shortHash } from "../utils/hash.js";
|
|
31
31
|
import { parseStreamingJson } from "../utils/json-parse.js";
|
|
32
32
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
|
|
33
|
-
import {
|
|
33
|
+
import { transformMessagesWithReport } from "./transform-messages.js";
|
|
34
34
|
|
|
35
35
|
// =============================================================================
|
|
36
36
|
// Utilities
|
|
@@ -108,7 +108,7 @@ export function convertResponsesMessages<TApi extends Api>(
|
|
|
108
108
|
return `${normalizedCallId}|${normalizedItemId}`;
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
-
const transformedMessages =
|
|
111
|
+
const transformedMessages = transformMessagesWithReport(context.messages, model, normalizeToolCallId, "openai-responses");
|
|
112
112
|
|
|
113
113
|
const includeSystemPrompt = options?.includeSystemPrompt ?? true;
|
|
114
114
|
if (includeSystemPrompt && context.systemPrompt) {
|