dsh-lcx-codex 0.4.2 → 0.4.3-pre.13
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/README.md +75 -224
- package/THIRD_PARTY_NOTICES.md +64 -0
- package/cordis.patch.yml +3 -20
- package/lib/auxiliary-usage.js +63 -0
- package/lib/client.js +1398 -167
- package/lib/compact-v2.js +218 -199
- package/lib/dsh-compat.js +294 -100
- package/lib/dsh-responses.js +512 -277
- package/lib/grok-native-search.js +391 -0
- package/lib/index.js +1066 -758
- package/lib/invocation-policy-scope.js +261 -0
- package/lib/json-store.js +57 -31
- package/lib/native-checkpoint.js +520 -194
- package/lib/pi-responses-runtime.js +1571 -0
- package/lib/responses-request.js +109 -121
- package/lib/responses-stream.js +1280 -447
- package/lib/route.js +425 -369
- package/lib/search-accounting.js +86 -0
- package/lib/search-usage.js +86 -0
- package/lib/service-mutex.js +73 -64
- package/lib/token-budget.js +176 -108
- package/lib/transport.js +308 -68
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/search-media.d.ts +16 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/web-run-output.js +189 -18
- package/lib/web-search-alpha.js +1067 -163
- package/lib/web-search-capability.js +80 -65
- package/lib/web-search-hosted.js +321 -33
- package/lib/web-search-ref-store.js +145 -60
- package/package.json +112 -32
- package/ARCHITECTURE.md +0 -117
- package/CHANGELOG.md +0 -224
- package/README_EN.md +0 -277
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/lib/legacy-v3.js +0 -20
- package/lib/responses-replay.js +0 -68
- package/scripts/probe-alpha.mjs +0 -43
- package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/dsh-responses.js
CHANGED
|
@@ -1,306 +1,541 @@
|
|
|
1
|
-
import { offloadRequestImagesWithPolicy } from
|
|
2
|
-
import { convertResponsesMessages, convertResponsesTools } from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function
|
|
11
|
-
|
|
1
|
+
import { offloadedImageText, offloadRequestImagesWithPolicy, requestImageHandleText, resolveImageAttachmentAccess, } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import { convertResponsesMessages, convertResponsesTools, createGrammarToolInputProperties, getBuiltinModels, } from "./pi-responses-runtime.js";
|
|
3
|
+
const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
4
|
+
const DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048;
|
|
5
|
+
const DEFAULT_REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
|
|
6
|
+
export { DEFAULT_MAX_REQUEST_IMAGE_BYTES, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET, DEFAULT_REQUEST_IMAGE_MAX_BYTES, };
|
|
7
|
+
function isObject(value) {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function isResponseInputItem(value) {
|
|
11
|
+
if (!isObject(value))
|
|
12
|
+
return false;
|
|
13
|
+
if (value.type === "compaction")
|
|
14
|
+
return typeof value.encrypted_content === "string";
|
|
15
|
+
if (value.type === "message" || value.type === undefined)
|
|
16
|
+
return ((value.role === "developer" ||
|
|
17
|
+
value.role === "user" ||
|
|
18
|
+
value.role === "system" ||
|
|
19
|
+
value.role === "assistant") &&
|
|
20
|
+
(typeof value.content === "string" || Array.isArray(value.content)));
|
|
21
|
+
if (value.type === "function_call")
|
|
22
|
+
return (typeof value.call_id === "string" &&
|
|
23
|
+
typeof value.name === "string" &&
|
|
24
|
+
typeof value.arguments === "string");
|
|
25
|
+
if (value.type === "function_call_output")
|
|
26
|
+
return typeof value.call_id === "string" && value.output !== undefined;
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
export function responseInputItems(value) {
|
|
30
|
+
if (!Array.isArray(value) || !value.every(isResponseInputItem))
|
|
31
|
+
throw error("native checkpoint contains an unsupported Responses input item", "LCX_COMPACT_INVALID_RESPONSE");
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
function asArray(value) {
|
|
35
|
+
return Array.isArray(value) ? value : [];
|
|
36
|
+
}
|
|
37
|
+
function stringValue(value, fallback = "") {
|
|
38
|
+
return typeof value === "string" ? value : fallback;
|
|
39
|
+
}
|
|
40
|
+
function positiveSafeInteger(value, fallback) {
|
|
41
|
+
return Number.isSafeInteger(value) && value > 0
|
|
42
|
+
? value
|
|
43
|
+
: fallback;
|
|
44
|
+
}
|
|
12
45
|
function error(message, code) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
46
|
+
const value = new Error(message);
|
|
47
|
+
value.code = code;
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
function errorCode(value) {
|
|
51
|
+
return isObject(value) && typeof value.code === "string" ? value.code : undefined;
|
|
52
|
+
}
|
|
53
|
+
function errorMessage(value) {
|
|
54
|
+
return value instanceof Error ? value.message : String(value);
|
|
55
|
+
}
|
|
56
|
+
function attachmentServices(store) {
|
|
57
|
+
if (store === undefined)
|
|
58
|
+
return undefined;
|
|
59
|
+
return {
|
|
60
|
+
store,
|
|
61
|
+
resolveAccess: (mapper, ref) => resolveImageAttachmentAccess(store, mapper, ref),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function hasImageAttachment(value) {
|
|
65
|
+
return isObject(value) && typeof value.attachmentId === "string" && typeof value.bytes === "number";
|
|
66
|
+
}
|
|
67
|
+
function imageAccess(ctx, attachments, ref) {
|
|
68
|
+
const mapper = ctx?.fs
|
|
69
|
+
? (hostPath) => ctx.fs.processPathFromHostPath(hostPath)
|
|
70
|
+
: () => undefined;
|
|
71
|
+
return attachments.resolveAccess(mapper, ref);
|
|
16
72
|
}
|
|
17
|
-
|
|
18
73
|
export async function resolveModelImageSupport(ctx, route, signal) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return async (block, signal) => {
|
|
34
|
-
if (typeof attachments?.readImageRequest !== 'function') {
|
|
35
|
-
throw error('LCX requires the DSH 0.1.1-rc.2 request-image attachment API', 'LCX_COMPACT_IMAGE_API_UNAVAILABLE')
|
|
74
|
+
const llm = ctx?.llm;
|
|
75
|
+
if (llm === undefined)
|
|
76
|
+
return "unknown";
|
|
77
|
+
try {
|
|
78
|
+
const info = await llm.resolveModelInfo(stringValue(route.provider), stringValue(route.model), signal);
|
|
79
|
+
const modalities = info.inputModalities;
|
|
80
|
+
return modalities?.includes("image")
|
|
81
|
+
? "supported"
|
|
82
|
+
: modalities !== undefined
|
|
83
|
+
? "unsupported"
|
|
84
|
+
: "unknown";
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return "unknown";
|
|
36
88
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
89
|
+
}
|
|
90
|
+
function attachmentResolver(ctx, options) {
|
|
91
|
+
const attachments = attachmentServices(ctx?.attachments);
|
|
92
|
+
return async (block, signal) => {
|
|
93
|
+
if (attachments === undefined)
|
|
94
|
+
throw error("LCX requires the DSH request-image attachment API", "LCX_COMPACT_IMAGE_API_UNAVAILABLE");
|
|
95
|
+
const request = await attachments.store.readImageRequest(block.attachment, { maxPixels: options.requestImagePixelBudget, maxBytes: options.requestImageMaxBytes }, signal);
|
|
96
|
+
const data = request.data;
|
|
97
|
+
const mediaType = request.mediaType ?? block.attachment.mediaType;
|
|
98
|
+
if (!(data instanceof Uint8Array) && !Buffer.isBuffer(data))
|
|
99
|
+
throw error("DSH attachment returned no request-image bytes", "LCX_COMPACT_IMAGE_UNAVAILABLE");
|
|
100
|
+
if (typeof mediaType !== "string" || !mediaType.startsWith("image/"))
|
|
101
|
+
throw error("DSH attachment returned an invalid request-image media type", "LCX_COMPACT_IMAGE_UNAVAILABLE");
|
|
102
|
+
const ref = request.attachment ?? block.attachment;
|
|
103
|
+
return {
|
|
104
|
+
data: Buffer.from(data),
|
|
105
|
+
mediaType,
|
|
106
|
+
ref,
|
|
107
|
+
request,
|
|
108
|
+
access: imageAccess(ctx, attachments, ref),
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
49
112
|
async function imagePart(block, ctx, options, imageMap) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
113
|
+
if (options.imageSupport === "unsupported")
|
|
114
|
+
return { type: "input_text", text: "[image omitted because the target model does not support image input]" };
|
|
115
|
+
const image = await attachmentResolver(ctx, options)(block, options.signal);
|
|
116
|
+
if (Math.ceil(image.data.byteLength / 3) * 4 > options.maxRequestImageBytes)
|
|
117
|
+
throw error("one image exceeds the configured LCX request image bound", "LCX_COMPACT_IMAGE_TOO_LARGE");
|
|
118
|
+
const imageUrl = `data:${image.mediaType};base64,${image.data.toString("base64")}`;
|
|
119
|
+
imageMap.set(imageUrl, structuredClone(image.ref));
|
|
120
|
+
return { type: "input_image", detail: "auto", image_url: imageUrl };
|
|
121
|
+
}
|
|
122
|
+
async function piImageParts(block, ctx, options, imageMap) {
|
|
123
|
+
if (options.imageSupport === "unsupported")
|
|
124
|
+
return [{ type: "text", text: "[image omitted because the target model does not support image input]" }];
|
|
125
|
+
const image = await attachmentResolver(ctx, options)(block, options.signal);
|
|
126
|
+
if (Math.ceil(image.data.byteLength / 3) * 4 > options.maxRequestImageBytes)
|
|
127
|
+
throw error("one image exceeds the configured LCX request image bound", "LCX_COMPACT_IMAGE_TOO_LARGE");
|
|
128
|
+
const data = image.data.toString("base64");
|
|
129
|
+
imageMap.set(`data:${image.mediaType};base64,${data}`, structuredClone(image.ref));
|
|
130
|
+
return [
|
|
131
|
+
{ type: "text", text: requestImageHandleText(image.ref, image.request, image.access) },
|
|
132
|
+
{ type: "image", data, mimeType: image.mediaType },
|
|
133
|
+
];
|
|
134
|
+
}
|
|
69
135
|
function parseArguments(value) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
136
|
+
if (isObject(value))
|
|
137
|
+
return structuredClone(value);
|
|
138
|
+
if (typeof value !== "string")
|
|
139
|
+
return {};
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(value);
|
|
142
|
+
return isObject(parsed) ? parsed : {};
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
75
147
|
}
|
|
76
|
-
|
|
77
148
|
function emptyUsage() {
|
|
78
|
-
|
|
149
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
|
|
150
|
+
}
|
|
151
|
+
function invalidReplay(message) {
|
|
152
|
+
return error(`invalid pi-ai replay state: ${message}`, "LCX_COMPACT_INVALID_REPLAY_STATE");
|
|
153
|
+
}
|
|
154
|
+
function unsupportedContent(type) {
|
|
155
|
+
return error(`LCX Compact cannot safely serialize DSH message content type: ${String(type)}`, "LCX_CHECKPOINT_PORTABLE_UNSUPPORTED_CONTENT");
|
|
156
|
+
}
|
|
157
|
+
function replayBlockType(type) {
|
|
158
|
+
return type === "text" || type === "reasoning" || type === "tool-call" ? type : undefined;
|
|
159
|
+
}
|
|
160
|
+
function replayTextPhase(signature) {
|
|
161
|
+
if (typeof signature !== "string" || !signature.startsWith("{"))
|
|
162
|
+
return undefined;
|
|
163
|
+
try {
|
|
164
|
+
const parsed = JSON.parse(signature);
|
|
165
|
+
if (!isObject(parsed) || parsed.v !== 1 || typeof parsed.id !== "string")
|
|
166
|
+
return undefined;
|
|
167
|
+
return parsed.phase === "commentary" || parsed.phase === "final_answer"
|
|
168
|
+
? parsed.phase
|
|
169
|
+
: undefined;
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function restorePortableAssistantPhases(input, messages, target) {
|
|
176
|
+
const slots = [];
|
|
177
|
+
for (const message of messages) {
|
|
178
|
+
if (message.role !== "assistant" || message.stopReason === "error" || message.stopReason === "aborted")
|
|
179
|
+
continue;
|
|
180
|
+
const sameProviderAndApi = message.provider === target.provider && message.api === target.api;
|
|
181
|
+
const sameModel = sameProviderAndApi && message.model === target.id;
|
|
182
|
+
for (const block of message.content) {
|
|
183
|
+
if (block.type === "text") {
|
|
184
|
+
const phase = sameProviderAndApi && !sameModel
|
|
185
|
+
? replayTextPhase(block.textSignature)
|
|
186
|
+
: undefined;
|
|
187
|
+
slots.push({ text: block.text, ...(phase === undefined ? {} : { phase }) });
|
|
188
|
+
}
|
|
189
|
+
else if (block.type === "thinking" && !sameModel && !block.redacted && block.thinking.trim()) {
|
|
190
|
+
slots.push({ text: block.thinking });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const output = structuredClone(input);
|
|
195
|
+
let slotIndex = 0;
|
|
196
|
+
for (const [index, candidate] of output.entries()) {
|
|
197
|
+
if (!isObject(candidate) || candidate.type !== "message" || candidate.role !== "assistant" ||
|
|
198
|
+
!Array.isArray(candidate.content))
|
|
199
|
+
continue;
|
|
200
|
+
const slot = slots[slotIndex++];
|
|
201
|
+
if (slot?.phase === undefined)
|
|
202
|
+
continue;
|
|
203
|
+
const text = candidate.content
|
|
204
|
+
.map((part) => isObject(part) && part.type === "output_text" ? String(part.text ?? "") : "")
|
|
205
|
+
.join("");
|
|
206
|
+
if (text === slot.text)
|
|
207
|
+
output[index] = { ...candidate, phase: slot.phase };
|
|
208
|
+
}
|
|
209
|
+
return output;
|
|
79
210
|
}
|
|
80
|
-
|
|
81
|
-
function invalidReplay(message) { return error(`invalid pi-ai replay state: ${message}`, 'LCX_COMPACT_INVALID_REPLAY_STATE') }
|
|
82
|
-
function unsupportedContent(type) { return error(`LCX Compact cannot safely serialize DSH message content type: ${String(type)}`, 'LCX_CHECKPOINT_PORTABLE_UNSUPPORTED_CONTENT') }
|
|
83
|
-
function replayBlockType(type) { if (type === 'text') return 'text'; if (type === 'reasoning') return 'reasoning'; if (type === 'tool-call') return 'tool-call'; return undefined }
|
|
84
|
-
|
|
85
211
|
export function readDshPiReplayState(value) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
212
|
+
if (!isObject(value))
|
|
213
|
+
throw invalidReplay("expected a replay envelope");
|
|
214
|
+
const responseValue = value.response;
|
|
215
|
+
if (!isObject(responseValue) || responseValue.kind !== "pi-ai" || responseValue.version !== 2)
|
|
216
|
+
throw invalidReplay("expected a pi-ai version 2 response object");
|
|
217
|
+
const required = ["api", "provider", "model"];
|
|
218
|
+
for (const key of required)
|
|
219
|
+
if (typeof responseValue[key] !== "string" || responseValue[key].length === 0)
|
|
220
|
+
throw invalidReplay(`${key} must be a non-empty string`);
|
|
221
|
+
if (!["stop", "length", "toolUse", "error", "aborted"].includes(responseValue.stopReason))
|
|
222
|
+
throw invalidReplay("unknown stopReason");
|
|
223
|
+
if (responseValue.responseModel !== undefined && typeof responseValue.responseModel !== "string")
|
|
224
|
+
throw invalidReplay("responseModel must be a string");
|
|
225
|
+
if (responseValue.responseId !== undefined && typeof responseValue.responseId !== "string")
|
|
226
|
+
throw invalidReplay("responseId must be a string");
|
|
227
|
+
if (!Array.isArray(value.blocks))
|
|
228
|
+
throw invalidReplay("blocks must be an array");
|
|
229
|
+
const blocks = value.blocks.map((block, index) => {
|
|
230
|
+
if (!isObject(block) || replayBlockType(block.type) === undefined)
|
|
231
|
+
throw invalidReplay(`block ${index} has an unknown type`);
|
|
232
|
+
for (const signature of ["textSignature", "thinkingSignature", "thoughtSignature", "namespace"])
|
|
233
|
+
if (block[signature] !== undefined && typeof block[signature] !== "string")
|
|
234
|
+
throw invalidReplay(`block ${index} ${signature} must be a string`);
|
|
235
|
+
if (block.redacted !== undefined && typeof block.redacted !== "boolean")
|
|
236
|
+
throw invalidReplay(`block ${index} redacted must be boolean`);
|
|
237
|
+
return { ...block, type: replayBlockType(block.type) };
|
|
238
|
+
});
|
|
239
|
+
return { response: responseValue, blocks };
|
|
240
|
+
}
|
|
106
241
|
function foreignAssistant(message) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
242
|
+
const source = message.source.kind === "model" ? message.source : undefined;
|
|
243
|
+
const content = [];
|
|
244
|
+
for (const block of message.content) {
|
|
245
|
+
if (block.type === "text")
|
|
246
|
+
content.push({ type: "text", text: block.text });
|
|
247
|
+
else if (block.type === "reasoning")
|
|
248
|
+
content.push({ type: "thinking", thinking: block.text });
|
|
249
|
+
else if (block.type === "tool-call")
|
|
250
|
+
content.push({ type: "toolCall", id: String(block.id), name: block.name, arguments: parseArguments(block.arguments) });
|
|
251
|
+
else
|
|
252
|
+
throw unsupportedContent(block.type);
|
|
253
|
+
}
|
|
254
|
+
return { role: "assistant", content, api: "dsh-foreign", provider: source?.provider ?? "dsh-foreign", model: source?.model ?? "dsh-foreign", usage: emptyUsage(), stopReason: content.some((block) => block.type === "toolCall") ? "toolUse" : "stop", timestamp: 0 };
|
|
255
|
+
}
|
|
119
256
|
function replayedAssistant(message, source) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
257
|
+
if (source.replayState === undefined)
|
|
258
|
+
throw invalidReplay("assistant source has no replay state");
|
|
259
|
+
const state = readDshPiReplayState(source.replayState);
|
|
260
|
+
if (state.response.provider !== source.provider)
|
|
261
|
+
throw invalidReplay("provider does not match assistant source");
|
|
262
|
+
if (state.response.model !== source.model)
|
|
263
|
+
throw invalidReplay("model does not match assistant source");
|
|
264
|
+
if (state.blocks.length !== message.content.length)
|
|
265
|
+
throw invalidReplay("block count does not match assistant content");
|
|
266
|
+
const content = message.content.map((block, index) => {
|
|
267
|
+
const replay = state.blocks[index];
|
|
268
|
+
if (replayBlockType(block.type) !== replay.type)
|
|
269
|
+
throw invalidReplay(`block ${index} does not match assistant content`);
|
|
270
|
+
if (block.type === "text")
|
|
271
|
+
return { type: "text", text: block.text, ...(typeof replay.textSignature === "string" ? { textSignature: replay.textSignature } : {}) };
|
|
272
|
+
if (block.type === "reasoning")
|
|
273
|
+
return { type: "thinking", thinking: block.text, ...(typeof replay.thinkingSignature === "string" ? { thinkingSignature: replay.thinkingSignature } : {}), ...(typeof replay.redacted === "boolean" ? { redacted: replay.redacted } : {}) };
|
|
274
|
+
if (block.type === "tool-call")
|
|
275
|
+
return { type: "toolCall", id: String(block.id), name: block.name, arguments: parseArguments(block.arguments), ...(typeof replay.thoughtSignature === "string" ? { thoughtSignature: replay.thoughtSignature } : {}), ...(typeof replay.namespace === "string" ? { namespace: replay.namespace } : {}) };
|
|
276
|
+
throw invalidReplay(`block ${index} has an unsupported type`);
|
|
277
|
+
});
|
|
278
|
+
return { role: "assistant", content, api: state.response.api, provider: state.response.provider, model: state.response.model, ...(typeof state.response.responseModel === "string" ? { responseModel: state.response.responseModel } : {}), ...(typeof state.response.responseId === "string" ? { responseId: state.response.responseId } : {}), usage: emptyUsage(), stopReason: state.response.stopReason, timestamp: 0 };
|
|
279
|
+
}
|
|
134
280
|
function toPiAssistant(message, onReplayDegrade) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
281
|
+
const source = message.source;
|
|
282
|
+
if (source.kind !== "model" || source.replayState === undefined)
|
|
283
|
+
return foreignAssistant(message);
|
|
284
|
+
try {
|
|
285
|
+
return replayedAssistant(message, source);
|
|
286
|
+
}
|
|
287
|
+
catch (cause) {
|
|
288
|
+
if (errorCode(cause) !== "LCX_COMPACT_INVALID_REPLAY_STATE")
|
|
289
|
+
throw cause;
|
|
290
|
+
if (typeof onReplayDegrade === "function")
|
|
291
|
+
onReplayDegrade(errorMessage(cause));
|
|
292
|
+
return foreignAssistant(message);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
145
295
|
async function piToolContent(blocks, ctx, options, imageMap) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
296
|
+
const content = [];
|
|
297
|
+
for (const block of blocks) {
|
|
298
|
+
if (block.type === "text")
|
|
299
|
+
content.push({ type: "text", text: block.text });
|
|
300
|
+
else if (block.type === "image")
|
|
301
|
+
content.push(...(await piImageParts(block, ctx, options, imageMap)));
|
|
302
|
+
else if (block.type === "reasoning")
|
|
303
|
+
continue;
|
|
304
|
+
else if (block.type === "tool-result")
|
|
305
|
+
content.push(...(await piToolContent(block.content, ctx, options, imageMap)));
|
|
306
|
+
else
|
|
307
|
+
throw unsupportedContent(block.type);
|
|
308
|
+
}
|
|
309
|
+
return content.length > 0 ? content : [{ type: "text", text: "(no output)" }];
|
|
310
|
+
}
|
|
311
|
+
function projectFileContent(blocks, fileRequestText) {
|
|
312
|
+
return blocks.map((block) => {
|
|
313
|
+
if (block.type === "file")
|
|
314
|
+
return { type: "text", text: fileRequestText(block.attachment) };
|
|
315
|
+
if (block.type === "tool-result")
|
|
316
|
+
return { ...block, content: projectFileContent(block.content, fileRequestText) };
|
|
317
|
+
return block;
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function projectFiles(messages, ctx) {
|
|
321
|
+
const fileRequestText = ctx?.llm?.fileRequestText.bind(ctx.llm);
|
|
322
|
+
if (fileRequestText === undefined) {
|
|
323
|
+
for (const message of messages)
|
|
324
|
+
for (const block of message.content)
|
|
325
|
+
if (block.type === "file")
|
|
326
|
+
throw unsupportedContent("file (DSH fileRequestText API unavailable)");
|
|
327
|
+
return messages;
|
|
328
|
+
}
|
|
329
|
+
return messages.map((message) => ({
|
|
330
|
+
...message,
|
|
331
|
+
content: projectFileContent(message.content, fileRequestText),
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
function flattenMessageText(message) {
|
|
335
|
+
return message.content
|
|
336
|
+
.map((block) => (block.type === "text" ? block.text : ""))
|
|
337
|
+
.join("");
|
|
338
|
+
}
|
|
155
339
|
async function dshToPiMessages(messages, ctx, options, imageMap) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
340
|
+
const result = [];
|
|
341
|
+
const toolNames = new Map();
|
|
342
|
+
for (const message of messages) {
|
|
343
|
+
if (message.role === "system") {
|
|
344
|
+
result.push({ role: "user", content: flattenMessageText(message), timestamp: 0 });
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (message.role === "assistant") {
|
|
348
|
+
const assistant = toPiAssistant(message, options.onReplayDegrade);
|
|
349
|
+
for (const block of assistant.content)
|
|
350
|
+
if (block.type === "toolCall")
|
|
351
|
+
toolNames.set(String(block.id), block.name);
|
|
352
|
+
result.push(assistant);
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const ordinary = [];
|
|
356
|
+
const toolResults = [];
|
|
357
|
+
for (const block of message.content) {
|
|
358
|
+
if (block.type === "text" || block.type === "image")
|
|
359
|
+
ordinary.push(block);
|
|
360
|
+
else if (block.type === "reasoning")
|
|
361
|
+
continue;
|
|
362
|
+
else if (block.type === "tool-result")
|
|
363
|
+
toolResults.push(block);
|
|
364
|
+
else
|
|
365
|
+
throw unsupportedContent(block.type);
|
|
366
|
+
}
|
|
367
|
+
if (ordinary.length > 0) {
|
|
368
|
+
const content = [];
|
|
369
|
+
for (const block of ordinary) {
|
|
370
|
+
if (block.type === "text")
|
|
371
|
+
content.push({ type: "text", text: block.text });
|
|
372
|
+
else if (block.type === "image")
|
|
373
|
+
content.push(...(await piImageParts(block, ctx, options, imageMap)));
|
|
374
|
+
}
|
|
375
|
+
if (content.length > 0)
|
|
376
|
+
result.push({ role: "user", content, timestamp: 0 });
|
|
377
|
+
}
|
|
378
|
+
for (const block of toolResults) {
|
|
379
|
+
if (block.type !== "tool-result")
|
|
380
|
+
continue;
|
|
381
|
+
const toolCallId = String(block.toolCallId);
|
|
382
|
+
result.push({ role: "toolResult", toolCallId, toolName: toolNames.get(toolCallId) ?? "unknown", content: await piToolContent(block.content, ctx, options, imageMap), addedToolNames: [], isError: block.isError === true, timestamp: 0 });
|
|
383
|
+
}
|
|
166
384
|
}
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
return result
|
|
385
|
+
return result;
|
|
170
386
|
}
|
|
171
|
-
|
|
172
387
|
function builtinResponsesModel(provider, modelId) {
|
|
173
|
-
|
|
174
|
-
|
|
388
|
+
try {
|
|
389
|
+
return getBuiltinModels(provider).find((model) => model.id === modelId && model.api === "openai-responses");
|
|
390
|
+
}
|
|
391
|
+
catch {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
175
394
|
}
|
|
176
|
-
|
|
177
395
|
export function resolvePiResponsesModel(options) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
baseUrl:
|
|
188
|
-
cost: explicit?.cost ?? builtin?.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: Number(explicit?.contextWindow ?? builtin?.contextWindow) > 0 ? Number(explicit?.contextWindow ?? builtin?.contextWindow) : 262144,
|
|
189
|
-
maxTokens: Number(explicit?.maxTokens ?? builtin?.maxTokens) > 0 ? Number(explicit?.maxTokens ?? builtin?.maxTokens) : 32768,
|
|
190
|
-
...(Object.keys(compat).length > 0 ? { compat } : {}),
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function splitDeferredTools(context, enabled) {
|
|
195
|
-
const unique = new Map()
|
|
196
|
-
for (const tool of context.tools ?? []) if (tool?.name) unique.set(tool.name, tool)
|
|
197
|
-
if (!enabled) return { immediate: [...unique.values()], deferred: new Map() }
|
|
198
|
-
const deferredNames = new Set(); const usedNames = new Set()
|
|
199
|
-
for (const message of context.messages ?? []) {
|
|
200
|
-
if (message.role === 'assistant') {
|
|
201
|
-
for (const block of message.content ?? []) if (block.type === 'toolCall') usedNames.add(block.name)
|
|
202
|
-
} else if (message.role === 'toolResult') {
|
|
203
|
-
for (const name of message.addedToolNames ?? []) if (!usedNames.has(name)) deferredNames.add(name)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
const immediate = []; const deferred = new Map()
|
|
207
|
-
for (const [name, tool] of unique) { if (deferredNames.has(name)) deferred.set(name, tool); else immediate.push(tool) }
|
|
208
|
-
return { immediate, deferred }
|
|
396
|
+
const route = isObject(options.route) ? options.route : {};
|
|
397
|
+
const explicit = isObject(options.model) ? options.model : {};
|
|
398
|
+
const provider = stringValue(explicit.provider, stringValue(route.provider, "dsh-lcx-codex"));
|
|
399
|
+
const id = stringValue(explicit.id, stringValue(route.model, "unknown"));
|
|
400
|
+
const builtin = builtinResponsesModel(provider, id);
|
|
401
|
+
const builtinRecord = isObject(builtin) ? builtin : {};
|
|
402
|
+
const compat = { ...(isObject(builtinRecord.compat) ? builtinRecord.compat : {}), ...(isObject(options.responsesCompat) ? options.responsesCompat : {}), ...(isObject(explicit.compat) ? explicit.compat : {}) };
|
|
403
|
+
const imageSupport = options.imageSupport === "supported" || options.imageSupport === "unsupported" ? options.imageSupport : "unknown";
|
|
404
|
+
const input = imageSupport === "supported" ? ["text", "image"] : imageSupport === "unsupported" ? ["text"] : Array.isArray(explicit.input) ? explicit.input : Array.isArray(builtinRecord.input) ? builtinRecord.input : ["text"];
|
|
405
|
+
return { ...builtinRecord, ...explicit, id, name: stringValue(explicit.name, stringValue(builtinRecord.name, id)), api: "openai-responses", provider, baseUrl: stringValue(explicit.baseUrl, stringValue(route.baseURL, stringValue(builtinRecord.baseUrl))), reasoning: typeof explicit.reasoning === "boolean" ? explicit.reasoning : typeof builtinRecord.reasoning === "boolean" ? builtinRecord.reasoning : true, input, cost: explicit.cost ?? builtinRecord.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: positiveSafeInteger(explicit.contextWindow ?? builtinRecord.contextWindow, 262144), maxTokens: positiveSafeInteger(explicit.maxTokens ?? builtinRecord.maxTokens, 32768), ...(Object.keys(compat).length > 0 ? { compat } : {}) };
|
|
209
406
|
}
|
|
210
|
-
|
|
211
407
|
export async function serializeDshMessages(messages, ctx, options = {}) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
408
|
+
const normalized = {
|
|
409
|
+
imageSupport: options.imageSupport === "supported" || options.imageSupport === "unsupported" ? options.imageSupport : "unknown",
|
|
410
|
+
signal: options.signal instanceof AbortSignal ? options.signal : undefined,
|
|
411
|
+
route: options.route ?? {}, model: options.model, responsesCompat: options.responsesCompat,
|
|
412
|
+
systemPrompt: typeof options.systemPrompt === "string" ? options.systemPrompt : undefined,
|
|
413
|
+
includeSystemPrompt: options.includeSystemPrompt === true, onReplayDegrade: options.onReplayDegrade,
|
|
414
|
+
maxRequestImageBytes: positiveSafeInteger(options.maxRequestImageBytes, DEFAULT_MAX_REQUEST_IMAGE_BYTES),
|
|
415
|
+
requestImagePixelBudget: positiveSafeInteger(options.requestImagePixelBudget, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
|
|
416
|
+
requestImageMaxBytes: positiveSafeInteger(options.requestImageMaxBytes, DEFAULT_REQUEST_IMAGE_MAX_BYTES),
|
|
417
|
+
tools: Array.isArray(options.tools) ? options.tools : [],
|
|
418
|
+
};
|
|
419
|
+
const imageMap = new Map();
|
|
420
|
+
const fileProjected = projectFiles(messages, ctx);
|
|
421
|
+
const attachments = attachmentServices(ctx?.attachments);
|
|
422
|
+
const projected = offloadRequestImagesWithPolicy(fileProjected, {
|
|
423
|
+
representation: "base64", maxBytes: normalized.maxRequestImageBytes, byteQuantum: 1,
|
|
424
|
+
byteLength: (ref) => Math.min(ref.bytes, normalized.requestImageMaxBytes),
|
|
425
|
+
placeholder: (ref) => offloadedImageText(ref, attachments === undefined ? undefined : imageAccess(ctx, attachments, ref)),
|
|
426
|
+
});
|
|
427
|
+
const model = resolvePiResponsesModel(normalized);
|
|
428
|
+
const context = { systemPrompt: normalized.systemPrompt, messages: await dshToPiMessages(projected, ctx, normalized, imageMap), tools: [...normalized.tools] };
|
|
429
|
+
const compat = isObject(model.compat) ? model.compat : {};
|
|
430
|
+
const supportsStrictMode = compat.supportsStrictMode === true;
|
|
431
|
+
const supportsOpenAIGrammarTools = compat.supportsOpenAIGrammarTools === true;
|
|
432
|
+
const supportsAdditionalTools = compat.supportsAdditionalTools === true;
|
|
433
|
+
const supportsToolSearch = compat.supportsToolSearch === true;
|
|
434
|
+
const deferredToolsMode = supportsAdditionalTools ? "additional-tools" : supportsToolSearch ? "tool-search" : undefined;
|
|
435
|
+
const grammarToolInputProperties = createGrammarToolInputProperties(context.tools, supportsOpenAIGrammarTools);
|
|
436
|
+
// DSH has no authoritative added-tool provenance; keep its full catalog immediate.
|
|
437
|
+
const immediateTools = new Map();
|
|
438
|
+
for (const tool of context.tools)
|
|
439
|
+
if (tool.name)
|
|
440
|
+
immediateTools.set(tool.name, tool);
|
|
441
|
+
const toolOptions = { supportsStrictMode, supportsOpenAIGrammarTools };
|
|
442
|
+
const piContext = {
|
|
443
|
+
systemPrompt: context.systemPrompt,
|
|
444
|
+
messages: context.messages,
|
|
445
|
+
tools: context.tools,
|
|
446
|
+
};
|
|
447
|
+
const piModel = model;
|
|
448
|
+
const convertedInput = convertResponsesMessages(piModel, piContext, new Set(["openai", "openai-codex", "opencode"]), { includeSystemPrompt: normalized.includeSystemPrompt, grammarToolInputProperties, deferredTools: new Map(), deferredToolsMode, toolOptions });
|
|
449
|
+
const input = restorePortableAssistantPhases(convertedInput, context.messages, { provider: model.provider, id: model.id, api: model.api });
|
|
450
|
+
const tools = options.tools === undefined ? undefined : convertResponsesTools([...immediateTools.values()], toolOptions);
|
|
451
|
+
return { input, imageMap, tools, model: piModel, grammarToolInputProperties, deferredToolsMode };
|
|
452
|
+
}
|
|
242
453
|
export function responsesTools(tools) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
454
|
+
if (tools === undefined)
|
|
455
|
+
return undefined;
|
|
456
|
+
if (!Array.isArray(tools))
|
|
457
|
+
throw error("LCX Responses tools must be an array", "LCX_COMPACT_INVALID_TOOLS");
|
|
458
|
+
if (tools.every((tool) => isObject(tool) && typeof tool.type === "string"))
|
|
459
|
+
return structuredClone(tools);
|
|
460
|
+
for (const tool of tools)
|
|
461
|
+
if (!isObject(tool) || typeof tool.name !== "string" || !tool.name)
|
|
462
|
+
throw error("LCX Responses tool has no name", "LCX_COMPACT_INVALID_TOOLS");
|
|
463
|
+
return convertResponsesTools(tools, { supportsStrictMode: false, supportsOpenAIGrammarTools: false });
|
|
464
|
+
}
|
|
465
|
+
function isResponsesMessageItem(item) {
|
|
466
|
+
return isObject(item) && typeof item.role === "string" && (item.type === undefined || item.type === "message");
|
|
467
|
+
}
|
|
252
468
|
function mapImageParts(value, mapper) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
469
|
+
return value.map((part) => {
|
|
470
|
+
if (!isObject(part))
|
|
471
|
+
return structuredClone(part);
|
|
472
|
+
if (part.type === "input_image" || part.type === "dsh_image_attachment")
|
|
473
|
+
return mapper(part);
|
|
474
|
+
if (part.type === "function_call_output" && Array.isArray(part.output))
|
|
475
|
+
return { ...structuredClone(part), output: mapImageParts(part.output, mapper) };
|
|
476
|
+
return structuredClone(part);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
261
479
|
export function persistNativeImageReferences(output, imageMap) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
480
|
+
const persist = (part) => {
|
|
481
|
+
if (part.type === "dsh_image_attachment" && isObject(part.attachment))
|
|
482
|
+
return structuredClone(part);
|
|
483
|
+
if (part.type !== "input_image" || typeof part.image_url !== "string")
|
|
484
|
+
throw error("native compact returned an invalid image item", "LCX_COMPACT_INVALID_RESPONSE");
|
|
485
|
+
const ref = imageMap.get(part.image_url);
|
|
486
|
+
if (ref === undefined)
|
|
487
|
+
throw error("native compact returned an untracked image item", "LCX_COMPACT_INVALID_RESPONSE");
|
|
488
|
+
return { type: "dsh_image_attachment", attachment: structuredClone(ref) };
|
|
489
|
+
};
|
|
490
|
+
return output.map((item) => {
|
|
491
|
+
if (!isObject(item))
|
|
492
|
+
return structuredClone(item);
|
|
493
|
+
if (isResponsesMessageItem(item) && Array.isArray(item.content))
|
|
494
|
+
return { ...structuredClone(item), content: mapImageParts(item.content, persist) };
|
|
495
|
+
if (item.type === "function_call_output" && Array.isArray(item.output))
|
|
496
|
+
return { ...structuredClone(item), output: mapImageParts(item.output, persist) };
|
|
497
|
+
return structuredClone(item);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
276
500
|
async function hydratePart(part, ctx, options) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
501
|
+
if (!isObject(part) || part.type !== "dsh_image_attachment")
|
|
502
|
+
return structuredClone(part);
|
|
503
|
+
const attachment = part.attachment;
|
|
504
|
+
if (!hasImageAttachment(attachment))
|
|
505
|
+
throw error("native compact returned an invalid image attachment", "LCX_COMPACT_INVALID_RESPONSE");
|
|
506
|
+
if (options.imageSupport === "unsupported")
|
|
507
|
+
return { type: "input_text", text: "[image omitted because the target model does not support image input]" };
|
|
508
|
+
return imagePart({ attachment }, ctx, options, options.imageMap);
|
|
282
509
|
}
|
|
283
|
-
|
|
284
510
|
export async function hydrateNativeImageReferences(output, ctx, options = {}) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
511
|
+
const normalized = {
|
|
512
|
+
imageSupport: options.imageSupport === "supported" || options.imageSupport === "unsupported" ? options.imageSupport : "unknown",
|
|
513
|
+
signal: options.signal instanceof AbortSignal ? options.signal : undefined,
|
|
514
|
+
maxRequestImageBytes: positiveSafeInteger(options.maxRequestImageBytes, DEFAULT_MAX_REQUEST_IMAGE_BYTES),
|
|
515
|
+
requestImagePixelBudget: positiveSafeInteger(options.requestImagePixelBudget, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
|
|
516
|
+
requestImageMaxBytes: positiveSafeInteger(options.requestImageMaxBytes, DEFAULT_REQUEST_IMAGE_MAX_BYTES),
|
|
517
|
+
imageMap: options.imageMap instanceof Map ? options.imageMap : new Map(),
|
|
518
|
+
};
|
|
519
|
+
const result = [];
|
|
520
|
+
for (const item of asArray(output)) {
|
|
521
|
+
if (!isObject(item)) {
|
|
522
|
+
result.push(structuredClone(item));
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (isResponsesMessageItem(item) && Array.isArray(item.content)) {
|
|
526
|
+
const content = [];
|
|
527
|
+
for (const part of item.content)
|
|
528
|
+
content.push(await hydratePart(part, ctx, normalized));
|
|
529
|
+
result.push({ ...structuredClone(item), content });
|
|
530
|
+
}
|
|
531
|
+
else if (item.type === "function_call_output" && Array.isArray(item.output)) {
|
|
532
|
+
const value = [];
|
|
533
|
+
for (const part of item.output)
|
|
534
|
+
value.push(await hydratePart(part, ctx, normalized));
|
|
535
|
+
result.push({ ...structuredClone(item), output: value });
|
|
536
|
+
}
|
|
537
|
+
else
|
|
538
|
+
result.push(structuredClone(item));
|
|
539
|
+
}
|
|
540
|
+
return result;
|
|
306
541
|
}
|