@cartanova/qgrid-ai-sdk 2.4.0 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +19 -0
- package/README.md +19 -0
- package/dist/index.js +219 -61
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -203,8 +203,27 @@ const result = await generateText({
|
|
|
203
203
|
const image = result.files[0]; // mediaType: "image/png", base64
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
+
레퍼런스 이미지는 일반 AI SDK multimodal message part로 전달할 수 있습니다:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const result = await generateText({
|
|
210
|
+
model: qgrid("openai/gpt-5.4"),
|
|
211
|
+
messages: [
|
|
212
|
+
{
|
|
213
|
+
role: "user",
|
|
214
|
+
content: [
|
|
215
|
+
{ type: "text", text: "이 이미지를 스타일 레퍼런스로 사용해서 포스터를 만들어줘" },
|
|
216
|
+
{ type: "file", mediaType: "image/png", data: referenceImageBase64 },
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
providerOptions: { qgrid: { imageGeneration: true } },
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
206
224
|
- `streamText`에서는 거부됩니다 (non-stream 전용).
|
|
207
225
|
- 이미지 생성 요청은 항상 cold one-shot thread로 실행되며 thread reuse 대상에서 제외됩니다.
|
|
226
|
+
- 레퍼런스 이미지는 JSON data URL로 전송됩니다. 큰 사진은 압축하거나 리사이즈해서 전달하세요. 과도하게 큰 base64 입력은 SDK가 거부하며, 사진에는 WebP/JPEG를 권장합니다.
|
|
208
227
|
- 이미지 비용은 `gpt-image-2` 공개 단가표 기반 **추정치**로 request log의 `image_cost_usd`에 별도 기록됩니다 (codex가 정확한 이미지 tool 사용량을 노출하지 않음).
|
|
209
228
|
|
|
210
229
|
## Telemetry Logger
|
package/README.md
CHANGED
|
@@ -203,8 +203,27 @@ const result = await generateText({
|
|
|
203
203
|
const image = result.files[0]; // mediaType: "image/png", base64
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
+
Reference images can be passed through normal AI SDK multimodal message parts:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const result = await generateText({
|
|
210
|
+
model: qgrid("openai/gpt-5.4"),
|
|
211
|
+
messages: [
|
|
212
|
+
{
|
|
213
|
+
role: "user",
|
|
214
|
+
content: [
|
|
215
|
+
{ type: "text", text: "Use this image as a style reference and create a poster" },
|
|
216
|
+
{ type: "file", mediaType: "image/png", data: referenceImageBase64 },
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
providerOptions: { qgrid: { imageGeneration: true } },
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
206
224
|
- Rejected in `streamText` (non-stream only).
|
|
207
225
|
- Image-generation requests always run on a cold one-shot thread and are excluded from thread reuse.
|
|
226
|
+
- Reference images are sent as JSON data URLs. Compress or resize large photos before passing them in; oversized base64 inputs are rejected by the SDK. WebP/JPEG is recommended for photos.
|
|
208
227
|
- The image cost is an **estimate** based on the public `gpt-image-2` price table, recorded separately as `image_cost_usd` on the request log (codex does not expose exact image-tool usage).
|
|
209
228
|
|
|
210
229
|
## Telemetry Logger
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ const QGRID_PROVIDER_NAME = "qgrid";
|
|
|
3
3
|
const THREAD_COORD_TTL_MS = 10 * 6e4;
|
|
4
4
|
const TEXT_STREAM_ID_PREFIX = "text";
|
|
5
5
|
const TOP_LEVEL_SCHEMA_WARNING = "OpenAI structured output requires \"object\". Falling back to client-side parsing.";
|
|
6
|
-
const PENDING_TOOL_RESULTS_WARNING = "[qgrid]
|
|
6
|
+
const PENDING_TOOL_RESULTS_WARNING = "[qgrid] tool results did not match any pending qgrid run; sending request without runContext";
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region src/utils.ts
|
|
9
9
|
async function createRun(serverUrl, body) {
|
|
@@ -84,7 +84,10 @@ async function* parseSSE(body) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
function extractPromptAndHistory(messages) {
|
|
87
|
+
function extractPromptAndHistory(messages, options = {}) {
|
|
88
|
+
const includeImages = options.includeImages ?? true;
|
|
89
|
+
const imageUrls = [];
|
|
90
|
+
let droppedImageCount = 0;
|
|
88
91
|
let system;
|
|
89
92
|
const nonSystem = [];
|
|
90
93
|
for (const msg of messages) if (msg.role === "system") system = extractTextFromContent(msg.content);
|
|
@@ -92,28 +95,51 @@ function extractPromptAndHistory(messages) {
|
|
|
92
95
|
if (nonSystem.length === 0) return {
|
|
93
96
|
prompt: "",
|
|
94
97
|
system,
|
|
95
|
-
history: []
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
prompt: extractTextFromContent(nonSystem[0].content),
|
|
99
|
-
system,
|
|
100
|
-
history: []
|
|
98
|
+
history: [],
|
|
99
|
+
imageUrls,
|
|
100
|
+
droppedImageCount
|
|
101
101
|
};
|
|
102
|
+
if (nonSystem.length === 1 && nonSystem[0].role === "user") {
|
|
103
|
+
const extracted = extractUserContent(nonSystem[0].content, includeImages);
|
|
104
|
+
imageUrls.push(...extracted.imageUrls);
|
|
105
|
+
droppedImageCount += extracted.droppedImageCount;
|
|
106
|
+
return {
|
|
107
|
+
prompt: extracted.text,
|
|
108
|
+
system,
|
|
109
|
+
history: [],
|
|
110
|
+
imageUrls,
|
|
111
|
+
droppedImageCount,
|
|
112
|
+
...extracted.input ? { input: extracted.input } : {}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
102
115
|
const last = nonSystem[nonSystem.length - 1];
|
|
103
|
-
|
|
116
|
+
let prompt = "";
|
|
117
|
+
let input;
|
|
118
|
+
if (last.role === "user") {
|
|
119
|
+
const extracted = extractUserContent(last.content, includeImages);
|
|
120
|
+
prompt = extracted.text;
|
|
121
|
+
imageUrls.push(...extracted.imageUrls);
|
|
122
|
+
droppedImageCount += extracted.droppedImageCount;
|
|
123
|
+
input = extracted.input;
|
|
124
|
+
}
|
|
104
125
|
const historyEnd = last.role === "user" ? nonSystem.length - 1 : nonSystem.length;
|
|
105
126
|
const history = [];
|
|
106
127
|
for (let i = 0; i < historyEnd; i++) {
|
|
107
128
|
const msg = nonSystem[i];
|
|
108
|
-
if (msg.role === "user")
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
if (msg.role === "user") {
|
|
130
|
+
const extracted = extractUserContent(msg.content, includeImages);
|
|
131
|
+
imageUrls.push(...extracted.imageUrls);
|
|
132
|
+
droppedImageCount += extracted.droppedImageCount;
|
|
133
|
+
const content = codexContentItemsFromExtracted(extracted.parts);
|
|
134
|
+
history.push({
|
|
135
|
+
type: "message",
|
|
136
|
+
role: "user",
|
|
137
|
+
content: content.length > 0 ? content : [{
|
|
138
|
+
type: "input_text",
|
|
139
|
+
text: extracted.text
|
|
140
|
+
}]
|
|
141
|
+
});
|
|
142
|
+
} else if (msg.role === "assistant") {
|
|
117
143
|
for (const part of msg.content) if ("text" in part && typeof part.text === "string") history.push({
|
|
118
144
|
type: "message",
|
|
119
145
|
role: "assistant",
|
|
@@ -143,7 +169,10 @@ function extractPromptAndHistory(messages) {
|
|
|
143
169
|
return {
|
|
144
170
|
prompt,
|
|
145
171
|
system,
|
|
146
|
-
history
|
|
172
|
+
history,
|
|
173
|
+
imageUrls,
|
|
174
|
+
droppedImageCount,
|
|
175
|
+
...input ? { input } : {}
|
|
147
176
|
};
|
|
148
177
|
}
|
|
149
178
|
function extractTextFromContent(content) {
|
|
@@ -152,6 +181,108 @@ function extractTextFromContent(content) {
|
|
|
152
181
|
for (const part of content) if ("text" in part && typeof part.text === "string") parts.push(part.text);
|
|
153
182
|
return parts.join("\n");
|
|
154
183
|
}
|
|
184
|
+
function extractUserContent(content, includeImages) {
|
|
185
|
+
const extracted = extractMessageParts(content, { includeImages });
|
|
186
|
+
return {
|
|
187
|
+
text: extractTextFromContent(content),
|
|
188
|
+
parts: extracted.parts,
|
|
189
|
+
input: inputPartsFromExtracted(extracted.parts),
|
|
190
|
+
imageUrls: extracted.imageUrls,
|
|
191
|
+
droppedImageCount: extracted.droppedImageCount
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function extractMessageParts(content, options) {
|
|
195
|
+
if (typeof content === "string") return {
|
|
196
|
+
parts: [{
|
|
197
|
+
kind: "text",
|
|
198
|
+
text: content
|
|
199
|
+
}],
|
|
200
|
+
imageUrls: [],
|
|
201
|
+
droppedImageCount: 0
|
|
202
|
+
};
|
|
203
|
+
const parts = [];
|
|
204
|
+
const imageUrls = [];
|
|
205
|
+
let droppedImageCount = 0;
|
|
206
|
+
for (const part of content) {
|
|
207
|
+
if ("text" in part && typeof part.text === "string") {
|
|
208
|
+
parts.push({
|
|
209
|
+
kind: "text",
|
|
210
|
+
text: part.text
|
|
211
|
+
});
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const url = extractImageUrl(part);
|
|
215
|
+
if (!url) continue;
|
|
216
|
+
if (options.includeImages) {
|
|
217
|
+
parts.push({
|
|
218
|
+
kind: "image",
|
|
219
|
+
url
|
|
220
|
+
});
|
|
221
|
+
imageUrls.push(url);
|
|
222
|
+
} else droppedImageCount++;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
parts,
|
|
226
|
+
imageUrls,
|
|
227
|
+
droppedImageCount
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function inputPartsFromExtracted(extractedParts) {
|
|
231
|
+
const parts = [];
|
|
232
|
+
for (const part of extractedParts) if (part.kind === "text") {
|
|
233
|
+
if (part.text.length > 0) parts.push({
|
|
234
|
+
type: "text",
|
|
235
|
+
text: part.text,
|
|
236
|
+
text_elements: []
|
|
237
|
+
});
|
|
238
|
+
} else parts.push({
|
|
239
|
+
type: "image",
|
|
240
|
+
url: part.url
|
|
241
|
+
});
|
|
242
|
+
return parts.some((part) => part.type === "image") ? parts : void 0;
|
|
243
|
+
}
|
|
244
|
+
function codexContentItemsFromExtracted(parts) {
|
|
245
|
+
const items = [];
|
|
246
|
+
for (const part of parts) if (part.kind === "text") items.push({
|
|
247
|
+
type: "input_text",
|
|
248
|
+
text: part.text
|
|
249
|
+
});
|
|
250
|
+
else items.push({
|
|
251
|
+
type: "input_image",
|
|
252
|
+
image_url: part.url
|
|
253
|
+
});
|
|
254
|
+
return items;
|
|
255
|
+
}
|
|
256
|
+
function extractImageUrl(part) {
|
|
257
|
+
if (!part || typeof part !== "object") return void 0;
|
|
258
|
+
const record = part;
|
|
259
|
+
const type = record.type;
|
|
260
|
+
if (type !== "file" && type !== "image") return void 0;
|
|
261
|
+
const mediaType = typeof record.mediaType === "string" ? record.mediaType : "image/png";
|
|
262
|
+
if (type === "file" && !mediaType.toLowerCase().startsWith("image/")) return void 0;
|
|
263
|
+
const data = record.data ?? record.image;
|
|
264
|
+
if (typeof data === "string") {
|
|
265
|
+
if (isSupportedImageUrl(data)) return data;
|
|
266
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(data)) return void 0;
|
|
267
|
+
return `data:${mediaType};base64,${data}`;
|
|
268
|
+
}
|
|
269
|
+
if (data instanceof URL) {
|
|
270
|
+
const url = data.toString();
|
|
271
|
+
return isSupportedImageUrl(url) ? url : void 0;
|
|
272
|
+
}
|
|
273
|
+
if (data instanceof Uint8Array) return `data:${mediaType};base64,${bytesToBase64(data)}`;
|
|
274
|
+
if (data instanceof ArrayBuffer) return `data:${mediaType};base64,${bytesToBase64(new Uint8Array(data))}`;
|
|
275
|
+
}
|
|
276
|
+
function isSupportedImageUrl(value) {
|
|
277
|
+
return /^(https?:|data:|blob:)/i.test(value);
|
|
278
|
+
}
|
|
279
|
+
function bytesToBase64(bytes) {
|
|
280
|
+
if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
|
|
281
|
+
const chunkSize = 32768;
|
|
282
|
+
let binary = "";
|
|
283
|
+
for (let i = 0; i < bytes.length; i += chunkSize) binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
|
|
284
|
+
return btoa(binary);
|
|
285
|
+
}
|
|
155
286
|
function safeStringify(value) {
|
|
156
287
|
try {
|
|
157
288
|
const json = JSON.stringify(value);
|
|
@@ -471,6 +602,21 @@ function createQgridLogger(config = {}) {
|
|
|
471
602
|
//#endregion
|
|
472
603
|
//#region src/index.ts
|
|
473
604
|
const threadCoordStore = /* @__PURE__ */ new Map();
|
|
605
|
+
const MAX_IMAGE_INPUT_DATA_URL_CHARS = 9e5;
|
|
606
|
+
const MAX_IMAGE_INPUT_DATA_URL_CHARS_LABEL = `${MAX_IMAGE_INPUT_DATA_URL_CHARS / 1e3}k`;
|
|
607
|
+
function assertImageInputFitsJsonTransport(imageUrls) {
|
|
608
|
+
let totalDataUrlChars = 0;
|
|
609
|
+
for (const url of imageUrls) {
|
|
610
|
+
if (!url.toLowerCase().startsWith("data:")) continue;
|
|
611
|
+
totalDataUrlChars += url.length;
|
|
612
|
+
if (url.length > MAX_IMAGE_INPUT_DATA_URL_CHARS) throw new Error(`qgrid: reference image input is too large for JSON transport (${url.length} chars). Compress or resize it before passing it to imageGeneration, for example as WebP/JPEG under ${MAX_IMAGE_INPUT_DATA_URL_CHARS_LABEL} total base64 data-url chars.`);
|
|
613
|
+
}
|
|
614
|
+
if (totalDataUrlChars > MAX_IMAGE_INPUT_DATA_URL_CHARS) throw new Error(`qgrid: reference image inputs are too large for JSON transport (${totalDataUrlChars} total chars). Compress or resize them before passing them to imageGeneration, for example as WebP/JPEG under ${MAX_IMAGE_INPUT_DATA_URL_CHARS_LABEL} total base64 data-url chars.`);
|
|
615
|
+
}
|
|
616
|
+
function warnDroppedImages(droppedImageCount) {
|
|
617
|
+
if (droppedImageCount <= 0) return;
|
|
618
|
+
console.warn(`[qgrid] ${droppedImageCount} image message part(s) were ignored because providerOptions.qgrid.imageGeneration is not enabled.`);
|
|
619
|
+
}
|
|
474
620
|
function getThreadCoord(sessionKey) {
|
|
475
621
|
const entry = threadCoordStore.get(sessionKey);
|
|
476
622
|
if (!entry) return void 0;
|
|
@@ -513,7 +659,39 @@ function qgrid(modelId, config) {
|
|
|
513
659
|
const serverUrl = config?.serverUrl ?? process.env.QGRID_URL ?? "http://localhost:44900";
|
|
514
660
|
const effort = config?.defaultEffort ?? "low";
|
|
515
661
|
const projectName = config?.projectName ?? process.env.QGRID_PROJECT_NAME;
|
|
516
|
-
|
|
662
|
+
const clientRuns = /* @__PURE__ */ new Map();
|
|
663
|
+
let anonymousClientRunId = 0;
|
|
664
|
+
function clientRunKey(runContext) {
|
|
665
|
+
if (runContext.requestLogId !== void 0) return `request:${runContext.requestLogId}`;
|
|
666
|
+
if (runContext.threadCoord) {
|
|
667
|
+
const { workerId, epoch, threadId } = runContext.threadCoord;
|
|
668
|
+
return `thread:${workerId}:${epoch}:${threadId}`;
|
|
669
|
+
}
|
|
670
|
+
anonymousClientRunId++;
|
|
671
|
+
return `anonymous:${anonymousClientRunId}`;
|
|
672
|
+
}
|
|
673
|
+
function rememberClientRun(runContext, toolCallIds) {
|
|
674
|
+
if (toolCallIds.length === 0) return;
|
|
675
|
+
clientRuns.set(clientRunKey(runContext), {
|
|
676
|
+
runContext,
|
|
677
|
+
pendingToolCallIds: new Set(toolCallIds)
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
function matchClientRun(messages) {
|
|
681
|
+
const toolResults = extractToolResultsFromHistory(messages);
|
|
682
|
+
if (toolResults.length === 0) return null;
|
|
683
|
+
const resultIds = new Set(toolResults.map((r) => r.callId));
|
|
684
|
+
for (const [key, run] of clientRuns) if (run.pendingToolCallIds.size > 0 && [...run.pendingToolCallIds].every((id) => resultIds.has(id))) return {
|
|
685
|
+
key,
|
|
686
|
+
runContext: run.runContext,
|
|
687
|
+
toolResultsPayload: toolResults.filter((r) => run.pendingToolCallIds.has(r.callId)).map((r) => ({
|
|
688
|
+
toolCallId: r.callId,
|
|
689
|
+
output: r.result
|
|
690
|
+
}))
|
|
691
|
+
};
|
|
692
|
+
if (clientRuns.size > 0) console.warn(PENDING_TOOL_RESULTS_WARNING);
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
517
695
|
return {
|
|
518
696
|
specificationVersion: "v3",
|
|
519
697
|
provider: QGRID_PROVIDER_NAME,
|
|
@@ -522,14 +700,16 @@ function qgrid(modelId, config) {
|
|
|
522
700
|
async doGenerate(options) {
|
|
523
701
|
const tools = options.tools?.filter((t) => t.type === "function");
|
|
524
702
|
const hasTools = tools && tools.length > 0;
|
|
525
|
-
const { prompt, system, history } = extractPromptAndHistory(options.prompt);
|
|
526
703
|
const qgridOptions = getQgridProviderOptions(options);
|
|
704
|
+
const imageGeneration = qgridOptions.imageGeneration;
|
|
705
|
+
const { prompt, system, history, input, imageUrls, droppedImageCount } = extractPromptAndHistory(options.prompt, { includeImages: imageGeneration === true });
|
|
706
|
+
if (!imageGeneration) warnDroppedImages(droppedImageCount);
|
|
527
707
|
const effectiveEffort = qgridOptions.effort ?? effort;
|
|
528
708
|
const verbosity = qgridOptions.verbosity;
|
|
529
709
|
const reasoningSummary = qgridOptions.reasoningSummary;
|
|
530
710
|
const serviceTier = qgridOptions.serviceTier;
|
|
531
|
-
const imageGeneration = qgridOptions.imageGeneration;
|
|
532
711
|
const imageGenerationOptions = qgridOptions.imageGenerationOptions;
|
|
712
|
+
if (imageGeneration) assertImageInputFitsJsonTransport(imageUrls);
|
|
533
713
|
const sessionKey = qgridOptions.sessionKey;
|
|
534
714
|
const reuseSessionKey = reusableSessionKey(modelId, sessionKey);
|
|
535
715
|
const rawSchema = options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0;
|
|
@@ -539,20 +719,11 @@ function qgrid(modelId, config) {
|
|
|
539
719
|
let runContext;
|
|
540
720
|
let toolResultsPayload;
|
|
541
721
|
let logMode;
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
toolResultsPayload = toolResults.filter((r) => clientRun.pendingToolCallIds.has(r.callId)).map((r) => ({
|
|
548
|
-
toolCallId: r.callId,
|
|
549
|
-
output: r.result
|
|
550
|
-
}));
|
|
551
|
-
logMode = "run";
|
|
552
|
-
} else {
|
|
553
|
-
console.warn(PENDING_TOOL_RESULTS_WARNING);
|
|
554
|
-
clientRun = null;
|
|
555
|
-
}
|
|
722
|
+
const matchedClientRun = matchClientRun(options.prompt);
|
|
723
|
+
if (matchedClientRun) {
|
|
724
|
+
runContext = matchedClientRun.runContext;
|
|
725
|
+
toolResultsPayload = matchedClientRun.toolResultsPayload;
|
|
726
|
+
logMode = "run";
|
|
556
727
|
}
|
|
557
728
|
const storedCoord = reuseSessionKey ? getThreadCoord(reuseSessionKey) : void 0;
|
|
558
729
|
if (!runContext && storedCoord) runContext = { threadCoord: storedCoord };
|
|
@@ -562,6 +733,7 @@ function qgrid(modelId, config) {
|
|
|
562
733
|
headers: { "Content-Type": "application/json" },
|
|
563
734
|
body: JSON.stringify({ args: {
|
|
564
735
|
prompt,
|
|
736
|
+
...imageGeneration && input ? { input } : {},
|
|
565
737
|
model: modelId,
|
|
566
738
|
system,
|
|
567
739
|
effort: effectiveEffort,
|
|
@@ -616,11 +788,8 @@ function qgrid(modelId, config) {
|
|
|
616
788
|
text: data.text
|
|
617
789
|
});
|
|
618
790
|
if (imageGeneration && !content.some((c) => c.type === "file")) throw new Error("qgrid: imageGeneration was requested but the response contained no image — the server may be an older version that does not support image generation.");
|
|
619
|
-
if (
|
|
620
|
-
|
|
621
|
-
pendingToolCallIds: new Set(content.filter((c) => c.type === "tool-call").map((c) => c.toolCallId))
|
|
622
|
-
};
|
|
623
|
-
else clientRun = null;
|
|
791
|
+
if (matchedClientRun) clientRuns.delete(matchedClientRun.key);
|
|
792
|
+
if (data.runContext && finishReason.unified === "tool-calls") rememberClientRun(data.runContext, content.filter((c) => c.type === "tool-call").map((c) => c.toolCallId));
|
|
624
793
|
if (reuseSessionKey && data.runContext?.threadCoord) setThreadCoord(reuseSessionKey, data.runContext.threadCoord);
|
|
625
794
|
return {
|
|
626
795
|
content,
|
|
@@ -639,13 +808,14 @@ function qgrid(modelId, config) {
|
|
|
639
808
|
async doStream(options) {
|
|
640
809
|
const tools = options.tools?.filter((t) => t.type === "function");
|
|
641
810
|
const hasTools = tools && tools.length > 0;
|
|
642
|
-
const { prompt, system, history } = extractPromptAndHistory(options.prompt);
|
|
643
811
|
const qgridOptions = getQgridProviderOptions(options);
|
|
644
812
|
const effectiveEffort = qgridOptions.effort ?? effort;
|
|
645
813
|
const verbosity = qgridOptions.verbosity;
|
|
646
814
|
const reasoningSummary = qgridOptions.reasoningSummary;
|
|
647
815
|
const serviceTier = qgridOptions.serviceTier;
|
|
648
816
|
if (qgridOptions.imageGeneration) throw new Error("qgrid: imageGeneration is not supported with streamText — use generateText instead.");
|
|
817
|
+
const { prompt, system, history, droppedImageCount } = extractPromptAndHistory(options.prompt, { includeImages: false });
|
|
818
|
+
warnDroppedImages(droppedImageCount);
|
|
649
819
|
const sessionKey = qgridOptions.sessionKey;
|
|
650
820
|
const reuseSessionKey = reusableSessionKey(modelId, sessionKey);
|
|
651
821
|
const rawSchema = options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0;
|
|
@@ -653,20 +823,11 @@ function qgrid(modelId, config) {
|
|
|
653
823
|
let runContext;
|
|
654
824
|
let toolResultsPayload;
|
|
655
825
|
let logMode;
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
toolResultsPayload = toolResults.filter((r) => clientRun.pendingToolCallIds.has(r.callId)).map((r) => ({
|
|
662
|
-
toolCallId: r.callId,
|
|
663
|
-
output: r.result
|
|
664
|
-
}));
|
|
665
|
-
logMode = "run";
|
|
666
|
-
} else {
|
|
667
|
-
console.warn(PENDING_TOOL_RESULTS_WARNING);
|
|
668
|
-
clientRun = null;
|
|
669
|
-
}
|
|
826
|
+
const matchedClientRun = matchClientRun(options.prompt);
|
|
827
|
+
if (matchedClientRun) {
|
|
828
|
+
runContext = matchedClientRun.runContext;
|
|
829
|
+
toolResultsPayload = matchedClientRun.toolResultsPayload;
|
|
830
|
+
logMode = "run";
|
|
670
831
|
}
|
|
671
832
|
const storedCoord = reuseSessionKey ? getThreadCoord(reuseSessionKey) : void 0;
|
|
672
833
|
if (!runContext && storedCoord) runContext = { threadCoord: storedCoord };
|
|
@@ -733,11 +894,8 @@ function qgrid(modelId, config) {
|
|
|
733
894
|
textStarted = false;
|
|
734
895
|
}
|
|
735
896
|
const done = event.data;
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
pendingToolCallIds: new Set((done.content ?? []).filter((c) => c.type === "tool-call").map((c) => c.toolCallId))
|
|
739
|
-
};
|
|
740
|
-
else clientRun = null;
|
|
897
|
+
if (matchedClientRun) clientRuns.delete(matchedClientRun.key);
|
|
898
|
+
if (done.runContext && done.finishReason === "tool-calls") rememberClientRun(done.runContext, (done.content ?? []).filter((c) => c.type === "tool-call").map((c) => c.toolCallId));
|
|
741
899
|
if (reuseSessionKey && done.runContext?.threadCoord) setThreadCoord(reuseSessionKey, done.runContext.threadCoord);
|
|
742
900
|
if (done.content) {
|
|
743
901
|
for (const item of done.content) if (item.type === "text" && !deltaTextEmitted) {
|