@cartanova/qgrid-ai-sdk 0.1.0

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/index.js ADDED
@@ -0,0 +1,750 @@
1
+ //#region src/utils.ts
2
+ async function createRun(serverUrl, body) {
3
+ return (await fetch(`${serverUrl}/api/qgrid/createRun`, {
4
+ method: "POST",
5
+ headers: { "Content-Type": "application/json" },
6
+ body: JSON.stringify({ input: body })
7
+ })).json();
8
+ }
9
+ async function appendStep(serverUrl, body) {
10
+ return (await fetch(`${serverUrl}/api/qgrid/appendStep`, {
11
+ method: "POST",
12
+ headers: { "Content-Type": "application/json" },
13
+ body: JSON.stringify({ input: body })
14
+ })).json();
15
+ }
16
+ async function finishRun(serverUrl, body) {
17
+ return (await fetch(`${serverUrl}/api/qgrid/finishRun`, {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify({ input: body })
21
+ })).json();
22
+ }
23
+ function toQgridTool(tool) {
24
+ const source = tool;
25
+ return {
26
+ name: tool.name,
27
+ description: tool.description,
28
+ inputSchema: source.inputSchema ?? source.parameters ?? {}
29
+ };
30
+ }
31
+ function extractToolResultsFromHistory(messages) {
32
+ const calls = /* @__PURE__ */ new Map();
33
+ const results = /* @__PURE__ */ new Map();
34
+ for (const msg of messages) if (msg.role === "assistant") {
35
+ for (const part of msg.content) if ("type" in part && part.type === "tool-call") calls.set(part.toolCallId, {
36
+ toolName: part.toolName,
37
+ args: typeof part.input === "string" ? part.input : JSON.stringify(part.input)
38
+ });
39
+ } else if (msg.role === "tool") {
40
+ for (const part of msg.content) if ("type" in part && part.type === "tool-result") {
41
+ const id = "toolCallId" in part ? part.toolCallId : "";
42
+ const output = part.output;
43
+ const text = "value" in output ? typeof output.value === "string" ? output.value : JSON.stringify(output.value) : JSON.stringify(output);
44
+ results.set(id, text);
45
+ }
46
+ }
47
+ const out = [];
48
+ for (const [callId, call] of calls) if (results.has(callId)) out.push({
49
+ callId,
50
+ toolName: call.toolName,
51
+ args: call.args,
52
+ result: results.get(callId)
53
+ });
54
+ return out;
55
+ }
56
+ async function* parseSSE(body) {
57
+ const reader = body.getReader();
58
+ const decoder = new TextDecoder();
59
+ let buffer = "";
60
+ let eventType = "";
61
+ for (;;) {
62
+ const { done, value } = await reader.read();
63
+ if (done) break;
64
+ buffer += decoder.decode(value, { stream: true });
65
+ const lines = buffer.split("\n");
66
+ buffer = lines.pop() ?? "";
67
+ for (const line of lines) if (line.startsWith("event: ")) eventType = line.slice(7).trim();
68
+ else if (line.startsWith("data: ")) {
69
+ const raw = line.slice(6);
70
+ try {
71
+ yield {
72
+ type: eventType || "message",
73
+ data: JSON.parse(raw)
74
+ };
75
+ } catch {}
76
+ eventType = "";
77
+ }
78
+ }
79
+ }
80
+ function extractPromptAndHistory(messages) {
81
+ let system;
82
+ const nonSystem = [];
83
+ for (const msg of messages) if (msg.role === "system") system = extractTextFromContent(msg.content);
84
+ else nonSystem.push(msg);
85
+ if (nonSystem.length === 0) return {
86
+ prompt: "",
87
+ system,
88
+ history: []
89
+ };
90
+ if (nonSystem.length === 1 && nonSystem[0].role === "user") return {
91
+ prompt: extractTextFromContent(nonSystem[0].content),
92
+ system,
93
+ history: []
94
+ };
95
+ const last = nonSystem[nonSystem.length - 1];
96
+ const prompt = last.role === "user" ? extractTextFromContent(last.content) : "";
97
+ const historyEnd = last.role === "user" ? nonSystem.length - 1 : nonSystem.length;
98
+ const history = [];
99
+ for (let i = 0; i < historyEnd; i++) {
100
+ const msg = nonSystem[i];
101
+ if (msg.role === "user") history.push({
102
+ type: "message",
103
+ role: "user",
104
+ content: [{
105
+ type: "input_text",
106
+ text: extractTextFromContent(msg.content)
107
+ }]
108
+ });
109
+ else if (msg.role === "assistant") {
110
+ for (const part of msg.content) if ("text" in part && typeof part.text === "string") history.push({
111
+ type: "message",
112
+ role: "assistant",
113
+ content: [{
114
+ type: "output_text",
115
+ text: part.text
116
+ }]
117
+ });
118
+ else if ("toolName" in part && part.type === "tool-call") history.push({
119
+ type: "function_call",
120
+ name: part.toolName,
121
+ arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input),
122
+ call_id: part.toolCallId
123
+ });
124
+ } else if (msg.role === "tool") {
125
+ for (const part of msg.content) if ("toolName" in part && part.type === "tool-result") {
126
+ const output = part.output;
127
+ const text = "value" in output ? typeof output.value === "string" ? output.value : JSON.stringify(output.value) : JSON.stringify(output);
128
+ history.push({
129
+ type: "function_call_output",
130
+ call_id: ("toolCallId" in part ? part.toolCallId : "") ?? "",
131
+ output: text
132
+ });
133
+ }
134
+ }
135
+ }
136
+ return {
137
+ prompt,
138
+ system,
139
+ history
140
+ };
141
+ }
142
+ function extractTextFromContent(content) {
143
+ if (typeof content === "string") return content;
144
+ const parts = [];
145
+ for (const part of content) if ("text" in part && typeof part.text === "string") parts.push(part.text);
146
+ return parts.join("\n");
147
+ }
148
+ function safeStringify(value) {
149
+ try {
150
+ const json = JSON.stringify(value);
151
+ return json === void 0 ? String(value) : json;
152
+ } catch {
153
+ return String(value);
154
+ }
155
+ }
156
+ function getRecord(value) {
157
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
158
+ }
159
+ function extractTextContent(content) {
160
+ if (typeof content === "string") return content;
161
+ const parts = [];
162
+ if (Array.isArray(content)) {
163
+ for (const part of content) if (part && typeof part === "object" && "type" in part) {
164
+ if (part.type === "text" && "text" in part && typeof part.text === "string") parts.push(part.text);
165
+ }
166
+ }
167
+ return parts.join("\n");
168
+ }
169
+ function getErrorMessage(value) {
170
+ if (value instanceof Error) return String(value);
171
+ if (value === void 0 || value === null) return "unknown error";
172
+ return String(value);
173
+ }
174
+ function extractUserPrompt(prompt, messages) {
175
+ if (typeof prompt === "string") return prompt;
176
+ const messageList = Array.isArray(messages) ? messages : Array.isArray(prompt) ? prompt : [];
177
+ for (let i = messageList.length - 1; i >= 0; i--) {
178
+ const msg = getRecord(messageList[i]);
179
+ if (msg?.role === "user") return extractTextContent(msg.content);
180
+ }
181
+ return "";
182
+ }
183
+ function extractSystemPrompt(system) {
184
+ if (typeof system === "string") return system;
185
+ if (system && typeof system === "object" && "content" in system) {
186
+ const content = system.content;
187
+ if (typeof content === "string") return content;
188
+ }
189
+ }
190
+ function serializeHistory(messages) {
191
+ if (!Array.isArray(messages) || messages.length === 0) return void 0;
192
+ const sliced = getRecord(messages[messages.length - 1])?.role === "user" ? messages.slice(0, -1) : messages;
193
+ const history = [];
194
+ for (const msg of sliced) {
195
+ const record = getRecord(msg);
196
+ if (record?.role !== "user" && record?.role !== "assistant") continue;
197
+ const text = extractTextContent(record.content);
198
+ if (text.length === 0) continue;
199
+ history.push({
200
+ type: "message",
201
+ role: record.role,
202
+ content: [{
203
+ type: record.role === "user" ? "input_text" : "output_text",
204
+ text
205
+ }]
206
+ });
207
+ }
208
+ if (history.length === 0) return void 0;
209
+ return safeStringify(history);
210
+ }
211
+ //#endregion
212
+ //#region src/logger.ts
213
+ const DEFAULT_RUN_KEY = "__qgrid_default_run__";
214
+ const DEFAULT_STALE_RUN_TIMEOUT_MS = 1800 * 1e3;
215
+ const STALE_RUN_GRACE_MS = 5e3;
216
+ function timedKeySet() {
217
+ const keys = /* @__PURE__ */ new Set();
218
+ const timers = /* @__PURE__ */ new Map();
219
+ return {
220
+ has: (k) => keys.has(k),
221
+ add(k, ttlMs) {
222
+ keys.add(k);
223
+ const existing = timers.get(k);
224
+ if (existing) clearTimeout(existing);
225
+ const t = setTimeout(() => {
226
+ keys.delete(k);
227
+ timers.delete(k);
228
+ }, ttlMs);
229
+ t.unref?.();
230
+ timers.set(k, t);
231
+ },
232
+ remove(k) {
233
+ keys.delete(k);
234
+ const t = timers.get(k);
235
+ if (t) clearTimeout(t);
236
+ timers.delete(k);
237
+ }
238
+ };
239
+ }
240
+ function createQgridLogger(config) {
241
+ const runs = /* @__PURE__ */ new Map();
242
+ const keyTtl = typeof config.staleRunTimeoutMs === "number" && config.staleRunTimeoutMs > 0 ? config.staleRunTimeoutMs : DEFAULT_STALE_RUN_TIMEOUT_MS;
243
+ const suppressedQgrid = timedKeySet();
244
+ const quarantined = timedKeySet();
245
+ const finalizeRun = async (runKey, result) => {
246
+ const run = runs.get(runKey);
247
+ if (!run || run.finishing) return;
248
+ run.finishing = true;
249
+ runs.delete(runKey);
250
+ if (run.watchdog) clearTimeout(run.watchdog);
251
+ run.cleanupAbortListener?.();
252
+ for (const pending of run.pendingToolCalls) run.pendingSteps.push(appendStep(config.serverUrl, {
253
+ requestLogId: run.requestLogId,
254
+ stepIndex: pending.stepIndex,
255
+ type: "tool_call",
256
+ toolCallIndex: pending.toolCallIndex,
257
+ toolCallId: pending.toolCallId,
258
+ toolName: pending.toolName,
259
+ toolArgs: pending.toolArgs,
260
+ toolDurationMs: run.toolDurations.get(pending.toolCallId)
261
+ }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e)))));
262
+ run.pendingToolCalls = [];
263
+ await Promise.allSettled(run.pendingSteps);
264
+ await finishRun(config.serverUrl, {
265
+ requestLogId: run.requestLogId,
266
+ status: result.status,
267
+ response: result.response,
268
+ tokenName: config.tokenName ?? "external",
269
+ totalInputTokens: result.totalUsage?.inputTokens ?? 0,
270
+ totalOutputTokens: result.totalUsage?.outputTokens ?? 0,
271
+ totalCacheReadTokens: result.totalUsage?.inputTokenDetails?.cacheReadTokens ?? 0,
272
+ totalCacheCreationTokens: result.totalUsage?.inputTokenDetails?.cacheWriteTokens ?? 0,
273
+ totalDurationMs: Date.now() - run.startTime,
274
+ history: run.history,
275
+ ...result.errorMessage ? { errorMessage: result.errorMessage } : {}
276
+ }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e))));
277
+ };
278
+ let autoRunIdCounter = 0;
279
+ const resolveRunKey = (event) => {
280
+ const qgridRunId = event.metadata?.qgridRunId;
281
+ if (typeof qgridRunId === "string" && qgridRunId.length > 0) return `qgridRunId:${qgridRunId}`;
282
+ if (typeof event.functionId === "string" && event.functionId.length > 0) return `functionId:${event.functionId}`;
283
+ return DEFAULT_RUN_KEY;
284
+ };
285
+ return {
286
+ isEnabled: true,
287
+ integrations: [{
288
+ async onStart(event) {
289
+ if (!event.metadata?.qgridRunId && !event.functionId && event.metadata) event.metadata.qgridRunId = `auto-${++autoRunIdCounter}`;
290
+ const runKey = resolveRunKey(event);
291
+ if (quarantined.has(runKey)) {
292
+ config.onLogError?.(/* @__PURE__ */ new Error("createQgridLogger: telemetry key is quarantined after overlap"));
293
+ return;
294
+ }
295
+ if (event.model.provider === "qgrid") {
296
+ suppressedQgrid.add(runKey, keyTtl);
297
+ return;
298
+ }
299
+ if (runs.has(runKey)) {
300
+ const msg = "createQgridLogger received overlapping runs for the same telemetry key. Pass a unique metadata.qgridRunId per AI SDK call or create a fresh logger integration per call.";
301
+ await finalizeRun(runKey, {
302
+ status: "error",
303
+ errorMessage: msg
304
+ });
305
+ quarantined.add(runKey, keyTtl);
306
+ config.onLogError?.(new Error(msg));
307
+ return;
308
+ }
309
+ try {
310
+ const messages = event.messages ?? (Array.isArray(event.prompt) ? event.prompt : void 0);
311
+ const result = await createRun(config.serverUrl, {
312
+ userPrompt: extractUserPrompt(event.prompt, messages),
313
+ systemPrompt: extractSystemPrompt(event.system),
314
+ modelName: event.model.modelId,
315
+ projectName: config.projectName
316
+ });
317
+ let watchdogTimeout = DEFAULT_STALE_RUN_TIMEOUT_MS;
318
+ if (typeof config.staleRunTimeoutMs === "number") watchdogTimeout = config.staleRunTimeoutMs;
319
+ else if (typeof event.timeout === "number" && event.timeout > 0) watchdogTimeout = event.timeout + STALE_RUN_GRACE_MS;
320
+ else {
321
+ const totalMs = getRecord(event.timeout)?.totalMs;
322
+ if (typeof totalMs === "number" && totalMs > 0) watchdogTimeout = totalMs + STALE_RUN_GRACE_MS;
323
+ }
324
+ let watchdog;
325
+ if (watchdogTimeout > 0) {
326
+ watchdog = setTimeout(() => {
327
+ finalizeRun(runKey, {
328
+ status: "error",
329
+ errorMessage: "AI SDK generation ended before onFinish was emitted"
330
+ });
331
+ }, watchdogTimeout);
332
+ watchdog.unref?.();
333
+ }
334
+ let cleanupAbortListener;
335
+ const signal = event.abortSignal;
336
+ if (signal) {
337
+ const onAbort = () => {
338
+ finalizeRun(runKey, {
339
+ status: "aborted",
340
+ errorMessage: getErrorMessage(signal.reason)
341
+ });
342
+ };
343
+ if (signal.aborted) queueMicrotask(onAbort);
344
+ else {
345
+ signal.addEventListener("abort", onAbort, { once: true });
346
+ cleanupAbortListener = () => signal.removeEventListener("abort", onAbort);
347
+ }
348
+ }
349
+ runs.set(runKey, {
350
+ requestLogId: result.requestLogId,
351
+ pendingSteps: [],
352
+ pendingToolCalls: [],
353
+ startTime: Date.now(),
354
+ toolDurations: /* @__PURE__ */ new Map(),
355
+ history: serializeHistory(messages),
356
+ watchdog,
357
+ cleanupAbortListener,
358
+ finishing: false
359
+ });
360
+ } catch (e) {
361
+ config.onLogError?.(e instanceof Error ? e : new Error(String(e)));
362
+ }
363
+ },
364
+ onToolCallFinish(event) {
365
+ const runKey = resolveRunKey(event);
366
+ if (suppressedQgrid.has(runKey) || quarantined.has(runKey)) return;
367
+ const run = runs.get(runKey);
368
+ if (!run) return;
369
+ run.toolDurations.set(event.toolCall.toolCallId, Math.round(event.durationMs));
370
+ },
371
+ onStepFinish(event) {
372
+ const runKey = resolveRunKey(event);
373
+ if (suppressedQgrid.has(runKey) || quarantined.has(runKey)) return;
374
+ const run = runs.get(runKey);
375
+ if (!run) return;
376
+ const { content, usage, reasoningText, finishReason, stepNumber } = event;
377
+ const remainingPending = [];
378
+ for (const pending of run.pendingToolCalls) {
379
+ const tr = content.find((p) => p.type === "tool-result" && p.toolCallId === pending.toolCallId);
380
+ const te = content.find((p) => p.type === "tool-error" && p.toolCallId === pending.toolCallId);
381
+ if (tr || te) {
382
+ run.pendingSteps.push(appendStep(config.serverUrl, {
383
+ requestLogId: run.requestLogId,
384
+ stepIndex: pending.stepIndex,
385
+ type: "tool_call",
386
+ toolCallIndex: pending.toolCallIndex,
387
+ toolCallId: pending.toolCallId,
388
+ toolName: pending.toolName,
389
+ toolArgs: pending.toolArgs,
390
+ toolResult: tr && "output" in tr ? safeStringify(tr.output) : void 0,
391
+ toolDurationMs: run.toolDurations.get(pending.toolCallId),
392
+ error: te && "error" in te ? safeStringify(te.error) : void 0
393
+ }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e)))));
394
+ run.toolDurations.delete(pending.toolCallId);
395
+ } else remainingPending.push(pending);
396
+ }
397
+ run.pendingToolCalls = remainingPending;
398
+ run.pendingSteps.push(appendStep(config.serverUrl, {
399
+ requestLogId: run.requestLogId,
400
+ stepIndex: stepNumber,
401
+ type: "generate",
402
+ inputTokens: usage.inputTokens ?? 0,
403
+ outputTokens: usage.outputTokens ?? 0,
404
+ cacheReadTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
405
+ cacheCreationTokens: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
406
+ finishReason,
407
+ reasoningText: typeof reasoningText === "string" && reasoningText.length > 0 ? reasoningText : void 0,
408
+ reasoningTokens: usage.outputTokenDetails?.reasoningTokens
409
+ }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e)))));
410
+ const toolCalls = content.filter((p) => p.type === "tool-call");
411
+ for (const [i, tc] of toolCalls.entries()) {
412
+ const tr = content.find((p) => p.type === "tool-result" && p.toolCallId === tc.toolCallId);
413
+ const te = content.find((p) => p.type === "tool-error" && p.toolCallId === tc.toolCallId);
414
+ if (tr || te) {
415
+ run.pendingSteps.push(appendStep(config.serverUrl, {
416
+ requestLogId: run.requestLogId,
417
+ stepIndex: stepNumber,
418
+ type: "tool_call",
419
+ toolCallIndex: i,
420
+ toolCallId: tc.toolCallId,
421
+ toolName: tc.toolName,
422
+ toolArgs: safeStringify(tc.input),
423
+ toolResult: tr && "output" in tr ? safeStringify(tr.output) : void 0,
424
+ toolDurationMs: run.toolDurations.get(tc.toolCallId),
425
+ error: te && "error" in te ? safeStringify(te.error) : void 0
426
+ }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e)))));
427
+ run.toolDurations.delete(tc.toolCallId);
428
+ } else run.pendingToolCalls.push({
429
+ stepIndex: stepNumber,
430
+ toolCallIndex: i,
431
+ toolCallId: tc.toolCallId,
432
+ toolName: tc.toolName,
433
+ toolArgs: safeStringify(tc.input)
434
+ });
435
+ }
436
+ },
437
+ async onFinish(event) {
438
+ const runKey = resolveRunKey(event);
439
+ if (suppressedQgrid.has(runKey)) {
440
+ suppressedQgrid.remove(runKey);
441
+ return;
442
+ }
443
+ if (quarantined.has(runKey)) return;
444
+ if (!runs.get(runKey)) return;
445
+ const status = event.finishReason === "error" ? "error" : "succeeded";
446
+ await finalizeRun(runKey, {
447
+ status,
448
+ response: event.text,
449
+ totalUsage: event.totalUsage,
450
+ ...status === "error" && "error" in event ? { errorMessage: getErrorMessage(event.error) } : {}
451
+ });
452
+ }
453
+ }]
454
+ };
455
+ }
456
+ //#endregion
457
+ //#region src/index.ts
458
+ const DEFAULT_SERVER_URL = "http://localhost:44900";
459
+ const DEFAULT_EFFORT = "low";
460
+ function qgrid(modelId, config) {
461
+ const serverUrl = config?.serverUrl ?? process.env.QGRID_URL ?? DEFAULT_SERVER_URL;
462
+ const effort = config?.defaultEffort ?? DEFAULT_EFFORT;
463
+ let clientRun = null;
464
+ return {
465
+ specificationVersion: "v3",
466
+ provider: "qgrid",
467
+ modelId,
468
+ supportedUrls: {},
469
+ async doGenerate(options) {
470
+ const tools = options.tools?.filter((t) => t.type === "function");
471
+ const hasTools = tools && tools.length > 0;
472
+ const { prompt, system, history } = extractPromptAndHistory(options.prompt);
473
+ const openaiOpts = options.providerOptions?.openai;
474
+ const effectiveEffort = openaiOpts?.reasoningEffort ?? effort;
475
+ const verbosity = openaiOpts?.textVerbosity ?? openaiOpts?.verbosity;
476
+ const reasoningSummary = openaiOpts?.reasoningSummary;
477
+ const rawSchema = options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0;
478
+ const schemaType = rawSchema ? rawSchema.type : void 0;
479
+ if (rawSchema && !hasTools && schemaType !== "object") console.warn(`[qgrid] responseFormat.schema top-level type is "${schemaType ?? "unknown"}". OpenAI structured output requires "object". Falling back to client-side parsing.`);
480
+ const jsonSchema = !hasTools && rawSchema && schemaType === "object" ? JSON.stringify(rawSchema) : void 0;
481
+ let runContext;
482
+ let toolResultsPayload;
483
+ let logMode;
484
+ if (clientRun) {
485
+ const toolResults = extractToolResultsFromHistory(options.prompt);
486
+ const resultIds = new Set(toolResults.map((r) => r.callId));
487
+ if (clientRun.pendingToolCallIds.size > 0 && [...clientRun.pendingToolCallIds].every((id) => resultIds.has(id))) {
488
+ runContext = clientRun.runContext;
489
+ toolResultsPayload = toolResults.filter((r) => clientRun.pendingToolCallIds.has(r.callId)).map((r) => ({
490
+ toolCallId: r.callId,
491
+ output: r.result
492
+ }));
493
+ logMode = "run";
494
+ } else {
495
+ console.warn("[qgrid] pending tool results not found in prompt, clearing client run state");
496
+ clientRun = null;
497
+ }
498
+ }
499
+ if (!logMode && hasTools) logMode = "run";
500
+ const data = await fetch(`${serverUrl}/api/qgrid/query`, {
501
+ method: "POST",
502
+ headers: { "Content-Type": "application/json" },
503
+ body: JSON.stringify({ args: {
504
+ prompt,
505
+ model: modelId,
506
+ system,
507
+ effort: effectiveEffort,
508
+ ...verbosity ? { verbosity } : {},
509
+ ...reasoningSummary ? { reasoningSummary } : {},
510
+ ...hasTools ? { tools: tools.map(toQgridTool) } : {},
511
+ ...jsonSchema ? { jsonSchema } : {},
512
+ ...history.length > 0 ? { history: JSON.stringify(history) } : {},
513
+ ...logMode ? { logMode } : {},
514
+ ...runContext ? { runContext } : {},
515
+ ...toolResultsPayload ? { toolResults: toolResultsPayload } : {}
516
+ } }),
517
+ signal: options.abortSignal
518
+ }).then(async (res) => {
519
+ if (!res.ok) {
520
+ const text = await res.text().catch(() => "");
521
+ throw new Error(`qgrid ${res.status}: ${text}`);
522
+ }
523
+ return await res.json();
524
+ });
525
+ const content = [];
526
+ let finishReason = {
527
+ unified: "stop",
528
+ raw: "stop"
529
+ };
530
+ if (data.content) {
531
+ for (const item of data.content) if (item.type === "text") content.push({
532
+ type: "text",
533
+ text: item.text
534
+ });
535
+ else content.push({
536
+ type: "tool-call",
537
+ toolCallId: item.toolCallId,
538
+ toolName: item.toolName,
539
+ input: item.input
540
+ });
541
+ if (data.finishReason === "tool-calls") finishReason = {
542
+ unified: "tool-calls",
543
+ raw: "tool_call"
544
+ };
545
+ } else content.push({
546
+ type: "text",
547
+ text: data.text
548
+ });
549
+ if (data.runContext && finishReason.unified === "tool-calls") clientRun = {
550
+ runContext: data.runContext,
551
+ pendingToolCallIds: new Set(content.filter((c) => c.type === "tool-call").map((c) => c.toolCallId))
552
+ };
553
+ else clientRun = null;
554
+ return {
555
+ content,
556
+ finishReason,
557
+ usage: {
558
+ inputTokens: {
559
+ total: data.usage.input_tokens,
560
+ noCache: data.usage.input_tokens - data.usage.cache_read_input_tokens,
561
+ cacheRead: data.usage.cache_read_input_tokens,
562
+ cacheWrite: data.usage.cache_creation_input_tokens
563
+ },
564
+ outputTokens: {
565
+ total: data.usage.output_tokens,
566
+ text: data.usage.output_tokens,
567
+ reasoning: void 0
568
+ }
569
+ },
570
+ warnings: [],
571
+ providerMetadata: { qgrid: {
572
+ model: data.model,
573
+ tokenName: data.tokenName ?? null,
574
+ durationMs: data.durationMs,
575
+ costUsd: data.costUsd
576
+ } },
577
+ response: { modelId: data.model }
578
+ };
579
+ },
580
+ async doStream(options) {
581
+ const tools = options.tools?.filter((t) => t.type === "function");
582
+ const hasTools = tools && tools.length > 0;
583
+ const { prompt, system, history } = extractPromptAndHistory(options.prompt);
584
+ const openaiOpts = options.providerOptions?.openai;
585
+ const effectiveEffort = openaiOpts?.reasoningEffort ?? effort;
586
+ const verbosity = openaiOpts?.textVerbosity ?? openaiOpts?.verbosity;
587
+ const reasoningSummary = openaiOpts?.reasoningSummary;
588
+ const rawSchema = options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0;
589
+ const jsonSchema = !hasTools && rawSchema && rawSchema.type === "object" ? JSON.stringify(rawSchema) : void 0;
590
+ let runContext;
591
+ let toolResultsPayload;
592
+ if (clientRun) {
593
+ const toolResults = extractToolResultsFromHistory(options.prompt);
594
+ const resultIds = new Set(toolResults.map((r) => r.callId));
595
+ if (clientRun.pendingToolCallIds.size > 0 && [...clientRun.pendingToolCallIds].every((id) => resultIds.has(id))) {
596
+ runContext = clientRun.runContext;
597
+ toolResultsPayload = toolResults.filter((r) => clientRun.pendingToolCallIds.has(r.callId)).map((r) => ({
598
+ toolCallId: r.callId,
599
+ output: r.result
600
+ }));
601
+ } else {
602
+ console.warn("[qgrid] pending tool results not found in prompt, clearing client run state");
603
+ clientRun = null;
604
+ }
605
+ }
606
+ const prepRes = await fetch(`${serverUrl}/api/qgrid/prepareStream`, {
607
+ method: "POST",
608
+ headers: { "Content-Type": "application/json" },
609
+ body: JSON.stringify({ args: {
610
+ prompt,
611
+ model: modelId,
612
+ system,
613
+ effort: effectiveEffort,
614
+ ...verbosity ? { verbosity } : {},
615
+ ...reasoningSummary ? { reasoningSummary } : {},
616
+ ...hasTools ? { tools: tools.map(toQgridTool) } : {},
617
+ ...jsonSchema ? { jsonSchema } : {},
618
+ ...history.length > 0 ? { history: JSON.stringify(history) } : {},
619
+ logMode: "run",
620
+ ...runContext ? { runContext } : {},
621
+ ...toolResultsPayload ? { toolResults: toolResultsPayload } : {}
622
+ } }),
623
+ signal: options.abortSignal
624
+ });
625
+ if (!prepRes.ok) {
626
+ const text = await prepRes.text().catch(() => "");
627
+ throw new Error(`qgrid prepareStream ${prepRes.status}: ${text}`);
628
+ }
629
+ const { streamId } = await prepRes.json();
630
+ const streamRes = await fetch(`${serverUrl}/api/qgrid/queryStream?streamId=${streamId}`, { signal: options.abortSignal });
631
+ if (!streamRes.ok || !streamRes.body) {
632
+ const text = await streamRes.text().catch(() => "");
633
+ throw new Error(`qgrid stream ${streamRes.status}: ${text}`);
634
+ }
635
+ const textId = `text_${Math.random().toString(36).slice(2, 10)}`;
636
+ let textStarted = false;
637
+ let deltaTextEmitted = false;
638
+ return { stream: new ReadableStream({ async start(controller) {
639
+ try {
640
+ let streamCompleted = false;
641
+ for await (const event of parseSSE(streamRes.body)) if (event.type === "delta") {
642
+ if (!hasTools) {
643
+ if (!textStarted) {
644
+ controller.enqueue({
645
+ type: "text-start",
646
+ id: textId
647
+ });
648
+ textStarted = true;
649
+ }
650
+ controller.enqueue({
651
+ type: "text-delta",
652
+ id: textId,
653
+ delta: event.data.text
654
+ });
655
+ deltaTextEmitted = true;
656
+ }
657
+ } else if (event.type === "done") {
658
+ if (textStarted) {
659
+ controller.enqueue({
660
+ type: "text-end",
661
+ id: textId
662
+ });
663
+ textStarted = false;
664
+ }
665
+ const done = event.data;
666
+ if (done.runContext && done.finishReason === "tool-calls") clientRun = {
667
+ runContext: done.runContext,
668
+ pendingToolCallIds: new Set((done.content ?? []).filter((c) => c.type === "tool-call").map((c) => c.toolCallId))
669
+ };
670
+ else clientRun = null;
671
+ if (done.content) {
672
+ for (const item of done.content) if (item.type === "text" && !deltaTextEmitted) {
673
+ const tid = `text_${Math.random().toString(36).slice(2, 10)}`;
674
+ controller.enqueue({
675
+ type: "text-start",
676
+ id: tid
677
+ });
678
+ controller.enqueue({
679
+ type: "text-delta",
680
+ id: tid,
681
+ delta: item.text
682
+ });
683
+ controller.enqueue({
684
+ type: "text-end",
685
+ id: tid
686
+ });
687
+ } else if (item.type === "tool-call") {
688
+ controller.enqueue({
689
+ type: "tool-input-start",
690
+ id: item.toolCallId,
691
+ toolName: item.toolName
692
+ });
693
+ controller.enqueue({
694
+ type: "tool-input-delta",
695
+ id: item.toolCallId,
696
+ delta: item.input
697
+ });
698
+ controller.enqueue({
699
+ type: "tool-input-end",
700
+ id: item.toolCallId
701
+ });
702
+ controller.enqueue({
703
+ type: "tool-call",
704
+ toolCallId: item.toolCallId,
705
+ toolName: item.toolName,
706
+ input: item.input
707
+ });
708
+ }
709
+ }
710
+ controller.enqueue({
711
+ type: "finish",
712
+ finishReason: done.finishReason === "tool-calls" ? {
713
+ unified: "tool-calls",
714
+ raw: "tool_call"
715
+ } : {
716
+ unified: "stop",
717
+ raw: "stop"
718
+ },
719
+ usage: {
720
+ inputTokens: {
721
+ total: done.usage.input_tokens,
722
+ noCache: done.usage.input_tokens - done.usage.cache_read_input_tokens,
723
+ cacheRead: done.usage.cache_read_input_tokens,
724
+ cacheWrite: done.usage.cache_creation_input_tokens
725
+ },
726
+ outputTokens: {
727
+ total: done.usage.output_tokens,
728
+ text: done.usage.output_tokens,
729
+ reasoning: void 0
730
+ }
731
+ }
732
+ });
733
+ streamCompleted = true;
734
+ controller.close();
735
+ return;
736
+ } else if (event.type === "error") {
737
+ streamCompleted = true;
738
+ controller.error(new Error(event.data.message));
739
+ return;
740
+ }
741
+ if (!streamCompleted) controller.error(/* @__PURE__ */ new Error("qgrid stream ended unexpectedly"));
742
+ } catch (e) {
743
+ controller.error(e);
744
+ }
745
+ } }) };
746
+ }
747
+ };
748
+ }
749
+ //#endregion
750
+ export { createQgridLogger, qgrid as default, qgrid };