@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/README.md +250 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +750 -0
- package/e2e/e2e-logger.ts +112 -0
- package/e2e/e2e.ts +217 -0
- package/package.json +31 -0
- package/src/index.test.ts +338 -0
- package/src/index.ts +396 -0
- package/src/index.types.ts +131 -0
- package/src/logger.test.ts +563 -0
- package/src/logger.ts +364 -0
- package/src/utils.ts +305 -0
- package/tsconfig.json +15 -0
- package/tsdown.config.ts +9 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type LanguageModelV3,
|
|
3
|
+
type LanguageModelV3CallOptions,
|
|
4
|
+
type LanguageModelV3Content,
|
|
5
|
+
type LanguageModelV3FinishReason,
|
|
6
|
+
type LanguageModelV3FunctionTool,
|
|
7
|
+
type LanguageModelV3GenerateResult,
|
|
8
|
+
type LanguageModelV3StreamPart,
|
|
9
|
+
type LanguageModelV3StreamResult,
|
|
10
|
+
} from "@ai-sdk/provider";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type QgridSupportedModel,
|
|
14
|
+
type QueryOutput,
|
|
15
|
+
type QgridProviderConfig,
|
|
16
|
+
} from "./index.types";
|
|
17
|
+
import {
|
|
18
|
+
extractPromptAndHistory,
|
|
19
|
+
extractToolResultsFromHistory,
|
|
20
|
+
parseSSE,
|
|
21
|
+
toQgridTool,
|
|
22
|
+
} from "./utils";
|
|
23
|
+
|
|
24
|
+
const DEFAULT_SERVER_URL = "http://localhost:44900";
|
|
25
|
+
const DEFAULT_EFFORT = "low";
|
|
26
|
+
|
|
27
|
+
export function qgrid(modelId: QgridSupportedModel, config?: QgridProviderConfig): LanguageModelV3 {
|
|
28
|
+
const serverUrl = config?.serverUrl ?? process.env.QGRID_URL ?? DEFAULT_SERVER_URL;
|
|
29
|
+
const effort = config?.defaultEffort ?? DEFAULT_EFFORT;
|
|
30
|
+
|
|
31
|
+
type ClientRunState = {
|
|
32
|
+
runContext: { requestLogId: number };
|
|
33
|
+
pendingToolCallIds: Set<string>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let clientRun: ClientRunState | null = null;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
specificationVersion: "v3",
|
|
40
|
+
provider: "qgrid",
|
|
41
|
+
modelId,
|
|
42
|
+
supportedUrls: {},
|
|
43
|
+
|
|
44
|
+
async doGenerate(options: LanguageModelV3CallOptions): Promise<LanguageModelV3GenerateResult> {
|
|
45
|
+
const tools = options.tools?.filter(
|
|
46
|
+
(t): t is LanguageModelV3FunctionTool => t.type === "function",
|
|
47
|
+
);
|
|
48
|
+
const hasTools = tools && tools.length > 0;
|
|
49
|
+
const { prompt, system, history } = extractPromptAndHistory(options.prompt);
|
|
50
|
+
|
|
51
|
+
const openaiOpts = options.providerOptions?.openai as Record<string, unknown> | undefined;
|
|
52
|
+
const effectiveEffort = (openaiOpts?.reasoningEffort as string) ?? effort;
|
|
53
|
+
const verbosity = (openaiOpts?.textVerbosity ?? openaiOpts?.verbosity) as string | undefined;
|
|
54
|
+
const reasoningSummary = openaiOpts?.reasoningSummary as string | undefined;
|
|
55
|
+
|
|
56
|
+
// top-level이 object인지 검사, 아니면 무시 (SDK 방어로직)
|
|
57
|
+
const rawSchema =
|
|
58
|
+
options.responseFormat?.type === "json" ? options.responseFormat.schema : undefined;
|
|
59
|
+
const schemaType = rawSchema ? (rawSchema as { type?: string }).type : undefined;
|
|
60
|
+
if (rawSchema && !hasTools && schemaType !== "object") {
|
|
61
|
+
console.warn(
|
|
62
|
+
`[qgrid] responseFormat.schema top-level type is "${schemaType ?? "unknown"}". OpenAI structured output requires "object". Falling back to client-side parsing.`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const jsonSchema =
|
|
66
|
+
!hasTools && rawSchema && schemaType === "object" ? JSON.stringify(rawSchema) : undefined;
|
|
67
|
+
|
|
68
|
+
// follow-up 판단 + toolResults 구성
|
|
69
|
+
let runContext: { requestLogId: number } | undefined;
|
|
70
|
+
let toolResultsPayload:
|
|
71
|
+
| Array<{ toolCallId: string; output: string; isError?: boolean }>
|
|
72
|
+
| undefined;
|
|
73
|
+
let logMode: "auto" | "run" | undefined;
|
|
74
|
+
|
|
75
|
+
if (clientRun) {
|
|
76
|
+
const toolResults = extractToolResultsFromHistory(options.prompt);
|
|
77
|
+
const resultIds = new Set(toolResults.map((r) => r.callId));
|
|
78
|
+
if (
|
|
79
|
+
clientRun.pendingToolCallIds.size > 0 &&
|
|
80
|
+
[...clientRun.pendingToolCallIds].every((id) => resultIds.has(id))
|
|
81
|
+
) {
|
|
82
|
+
// follow-up
|
|
83
|
+
runContext = clientRun.runContext;
|
|
84
|
+
toolResultsPayload = toolResults
|
|
85
|
+
.filter((r) => clientRun!.pendingToolCallIds.has(r.callId))
|
|
86
|
+
.map((r) => ({ toolCallId: r.callId, output: r.result }));
|
|
87
|
+
logMode = "run";
|
|
88
|
+
} else {
|
|
89
|
+
console.warn(
|
|
90
|
+
"[qgrid] pending tool results not found in prompt, clearing client run state",
|
|
91
|
+
);
|
|
92
|
+
clientRun = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!logMode && hasTools) logMode = "run";
|
|
97
|
+
|
|
98
|
+
const data = await fetch(`${serverUrl}/api/qgrid/query`, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: { "Content-Type": "application/json" },
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
args: {
|
|
103
|
+
prompt,
|
|
104
|
+
model: modelId,
|
|
105
|
+
system,
|
|
106
|
+
effort: effectiveEffort,
|
|
107
|
+
...(verbosity ? { verbosity } : {}),
|
|
108
|
+
...(reasoningSummary ? { reasoningSummary } : {}),
|
|
109
|
+
...(hasTools ? { tools: tools.map(toQgridTool) } : {}),
|
|
110
|
+
...(jsonSchema ? { jsonSchema } : {}),
|
|
111
|
+
...(history.length > 0 ? { history: JSON.stringify(history) } : {}),
|
|
112
|
+
...(logMode ? { logMode } : {}),
|
|
113
|
+
...(runContext ? { runContext } : {}),
|
|
114
|
+
...(toolResultsPayload ? { toolResults: toolResultsPayload } : {}),
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
signal: options.abortSignal,
|
|
118
|
+
}).then(async (res) => {
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
const text = await res.text().catch(() => "");
|
|
121
|
+
throw new Error(`qgrid ${res.status}: ${text}`);
|
|
122
|
+
}
|
|
123
|
+
return (await res.json()) as QueryOutput;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// 응답 변환
|
|
127
|
+
const content: LanguageModelV3Content[] = [];
|
|
128
|
+
let finishReason: LanguageModelV3FinishReason = { unified: "stop", raw: "stop" };
|
|
129
|
+
if (data.content) {
|
|
130
|
+
for (const item of data.content) {
|
|
131
|
+
if (item.type === "text") content.push({ type: "text", text: item.text });
|
|
132
|
+
else
|
|
133
|
+
content.push({
|
|
134
|
+
type: "tool-call",
|
|
135
|
+
toolCallId: item.toolCallId,
|
|
136
|
+
toolName: item.toolName,
|
|
137
|
+
input: item.input,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (data.finishReason === "tool-calls")
|
|
141
|
+
finishReason = { unified: "tool-calls", raw: "tool_call" };
|
|
142
|
+
} else {
|
|
143
|
+
content.push({ type: "text", text: data.text });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// client run state 업데이트
|
|
147
|
+
if (data.runContext && finishReason.unified === "tool-calls") {
|
|
148
|
+
clientRun = {
|
|
149
|
+
runContext: data.runContext,
|
|
150
|
+
pendingToolCallIds: new Set(
|
|
151
|
+
content
|
|
152
|
+
.filter(
|
|
153
|
+
(c): c is Extract<LanguageModelV3Content, { type: "tool-call" }> =>
|
|
154
|
+
c.type === "tool-call",
|
|
155
|
+
)
|
|
156
|
+
.map((c) => c.toolCallId),
|
|
157
|
+
),
|
|
158
|
+
};
|
|
159
|
+
} else {
|
|
160
|
+
clientRun = null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
content,
|
|
165
|
+
finishReason,
|
|
166
|
+
usage: {
|
|
167
|
+
inputTokens: {
|
|
168
|
+
total: data.usage.input_tokens,
|
|
169
|
+
noCache: data.usage.input_tokens - data.usage.cache_read_input_tokens,
|
|
170
|
+
cacheRead: data.usage.cache_read_input_tokens,
|
|
171
|
+
cacheWrite: data.usage.cache_creation_input_tokens,
|
|
172
|
+
},
|
|
173
|
+
outputTokens: {
|
|
174
|
+
total: data.usage.output_tokens,
|
|
175
|
+
text: data.usage.output_tokens,
|
|
176
|
+
reasoning: undefined,
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
warnings: [],
|
|
180
|
+
providerMetadata: {
|
|
181
|
+
qgrid: {
|
|
182
|
+
model: data.model,
|
|
183
|
+
tokenName: data.tokenName ?? null,
|
|
184
|
+
durationMs: data.durationMs,
|
|
185
|
+
costUsd: data.costUsd,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
response: { modelId: data.model },
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
async doStream(options: LanguageModelV3CallOptions): Promise<LanguageModelV3StreamResult> {
|
|
193
|
+
const tools = options.tools?.filter(
|
|
194
|
+
(t): t is LanguageModelV3FunctionTool => t.type === "function",
|
|
195
|
+
);
|
|
196
|
+
const hasTools = tools && tools.length > 0;
|
|
197
|
+
const { prompt, system, history } = extractPromptAndHistory(options.prompt);
|
|
198
|
+
|
|
199
|
+
const openaiOpts = options.providerOptions?.openai as Record<string, unknown> | undefined;
|
|
200
|
+
const effectiveEffort = (openaiOpts?.reasoningEffort as string) ?? effort;
|
|
201
|
+
const verbosity = (openaiOpts?.textVerbosity ?? openaiOpts?.verbosity) as string | undefined;
|
|
202
|
+
const reasoningSummary = openaiOpts?.reasoningSummary as string | undefined;
|
|
203
|
+
|
|
204
|
+
const rawSchema =
|
|
205
|
+
options.responseFormat?.type === "json" ? options.responseFormat.schema : undefined;
|
|
206
|
+
const jsonSchema =
|
|
207
|
+
!hasTools && rawSchema && (rawSchema as { type?: string }).type === "object"
|
|
208
|
+
? JSON.stringify(rawSchema)
|
|
209
|
+
: undefined;
|
|
210
|
+
|
|
211
|
+
// follow-up 판단
|
|
212
|
+
let runContext: { requestLogId: number } | undefined;
|
|
213
|
+
let toolResultsPayload:
|
|
214
|
+
| Array<{ toolCallId: string; output: string; isError?: boolean }>
|
|
215
|
+
| undefined;
|
|
216
|
+
|
|
217
|
+
if (clientRun) {
|
|
218
|
+
const toolResults = extractToolResultsFromHistory(options.prompt);
|
|
219
|
+
const resultIds = new Set(toolResults.map((r) => r.callId));
|
|
220
|
+
if (
|
|
221
|
+
clientRun.pendingToolCallIds.size > 0 &&
|
|
222
|
+
[...clientRun.pendingToolCallIds].every((id) => resultIds.has(id))
|
|
223
|
+
) {
|
|
224
|
+
runContext = clientRun.runContext;
|
|
225
|
+
toolResultsPayload = toolResults
|
|
226
|
+
.filter((r) => clientRun!.pendingToolCallIds.has(r.callId))
|
|
227
|
+
.map((r) => ({ toolCallId: r.callId, output: r.result }));
|
|
228
|
+
} else {
|
|
229
|
+
console.warn(
|
|
230
|
+
"[qgrid] pending tool results not found in prompt, clearing client run state",
|
|
231
|
+
);
|
|
232
|
+
clientRun = null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const prepRes = await fetch(`${serverUrl}/api/qgrid/prepareStream`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: { "Content-Type": "application/json" },
|
|
239
|
+
body: JSON.stringify({
|
|
240
|
+
args: {
|
|
241
|
+
prompt,
|
|
242
|
+
model: modelId,
|
|
243
|
+
system,
|
|
244
|
+
effort: effectiveEffort,
|
|
245
|
+
...(verbosity ? { verbosity } : {}),
|
|
246
|
+
...(reasoningSummary ? { reasoningSummary } : {}),
|
|
247
|
+
...(hasTools ? { tools: tools.map(toQgridTool) } : {}),
|
|
248
|
+
...(jsonSchema ? { jsonSchema } : {}),
|
|
249
|
+
...(history.length > 0 ? { history: JSON.stringify(history) } : {}),
|
|
250
|
+
logMode: "run",
|
|
251
|
+
...(runContext ? { runContext } : {}),
|
|
252
|
+
...(toolResultsPayload ? { toolResults: toolResultsPayload } : {}),
|
|
253
|
+
},
|
|
254
|
+
}),
|
|
255
|
+
signal: options.abortSignal,
|
|
256
|
+
});
|
|
257
|
+
if (!prepRes.ok) {
|
|
258
|
+
const text = await prepRes.text().catch(() => "");
|
|
259
|
+
throw new Error(`qgrid prepareStream ${prepRes.status}: ${text}`);
|
|
260
|
+
}
|
|
261
|
+
const { streamId } = (await prepRes.json()) as { streamId: string };
|
|
262
|
+
|
|
263
|
+
const streamRes = await fetch(`${serverUrl}/api/qgrid/queryStream?streamId=${streamId}`, {
|
|
264
|
+
signal: options.abortSignal,
|
|
265
|
+
});
|
|
266
|
+
if (!streamRes.ok || !streamRes.body) {
|
|
267
|
+
const text = await streamRes.text().catch(() => "");
|
|
268
|
+
throw new Error(`qgrid stream ${streamRes.status}: ${text}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const textId = `text_${Math.random().toString(36).slice(2, 10)}`;
|
|
272
|
+
let textStarted = false;
|
|
273
|
+
let deltaTextEmitted = false;
|
|
274
|
+
|
|
275
|
+
const stream = new ReadableStream<LanguageModelV3StreamPart>({
|
|
276
|
+
async start(controller) {
|
|
277
|
+
try {
|
|
278
|
+
let streamCompleted = false;
|
|
279
|
+
for await (const event of parseSSE(streamRes.body!)) {
|
|
280
|
+
if (event.type === "delta") {
|
|
281
|
+
if (!hasTools) {
|
|
282
|
+
if (!textStarted) {
|
|
283
|
+
controller.enqueue({ type: "text-start", id: textId });
|
|
284
|
+
textStarted = true;
|
|
285
|
+
}
|
|
286
|
+
controller.enqueue({
|
|
287
|
+
type: "text-delta",
|
|
288
|
+
id: textId,
|
|
289
|
+
delta: (event.data as { text: string }).text,
|
|
290
|
+
});
|
|
291
|
+
deltaTextEmitted = true;
|
|
292
|
+
}
|
|
293
|
+
} else if (event.type === "done") {
|
|
294
|
+
if (textStarted) {
|
|
295
|
+
controller.enqueue({ type: "text-end", id: textId });
|
|
296
|
+
textStarted = false;
|
|
297
|
+
}
|
|
298
|
+
const done = event.data as QueryOutput;
|
|
299
|
+
|
|
300
|
+
// client run state 업데이트
|
|
301
|
+
if (done.runContext && done.finishReason === "tool-calls") {
|
|
302
|
+
clientRun = {
|
|
303
|
+
runContext: done.runContext,
|
|
304
|
+
pendingToolCallIds: new Set(
|
|
305
|
+
(done.content ?? [])
|
|
306
|
+
.filter(
|
|
307
|
+
(
|
|
308
|
+
c,
|
|
309
|
+
): c is Extract<
|
|
310
|
+
NonNullable<QueryOutput["content"]>[number],
|
|
311
|
+
{ type: "tool-call" }
|
|
312
|
+
> => c.type === "tool-call",
|
|
313
|
+
)
|
|
314
|
+
.map((c) => c.toolCallId),
|
|
315
|
+
),
|
|
316
|
+
};
|
|
317
|
+
} else {
|
|
318
|
+
clientRun = null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// AI SDK stream parts
|
|
322
|
+
if (done.content) {
|
|
323
|
+
for (const item of done.content) {
|
|
324
|
+
if (item.type === "text" && !deltaTextEmitted) {
|
|
325
|
+
const tid = `text_${Math.random().toString(36).slice(2, 10)}`;
|
|
326
|
+
controller.enqueue({ type: "text-start", id: tid });
|
|
327
|
+
controller.enqueue({ type: "text-delta", id: tid, delta: item.text });
|
|
328
|
+
controller.enqueue({ type: "text-end", id: tid });
|
|
329
|
+
} else if (item.type === "tool-call") {
|
|
330
|
+
controller.enqueue({
|
|
331
|
+
type: "tool-input-start",
|
|
332
|
+
id: item.toolCallId,
|
|
333
|
+
toolName: item.toolName,
|
|
334
|
+
});
|
|
335
|
+
controller.enqueue({
|
|
336
|
+
type: "tool-input-delta",
|
|
337
|
+
id: item.toolCallId,
|
|
338
|
+
delta: item.input,
|
|
339
|
+
});
|
|
340
|
+
controller.enqueue({ type: "tool-input-end", id: item.toolCallId });
|
|
341
|
+
controller.enqueue({
|
|
342
|
+
type: "tool-call",
|
|
343
|
+
toolCallId: item.toolCallId,
|
|
344
|
+
toolName: item.toolName,
|
|
345
|
+
input: item.input,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
controller.enqueue({
|
|
352
|
+
type: "finish",
|
|
353
|
+
finishReason:
|
|
354
|
+
done.finishReason === "tool-calls"
|
|
355
|
+
? { unified: "tool-calls", raw: "tool_call" }
|
|
356
|
+
: { unified: "stop", raw: "stop" },
|
|
357
|
+
usage: {
|
|
358
|
+
inputTokens: {
|
|
359
|
+
total: done.usage.input_tokens,
|
|
360
|
+
noCache: done.usage.input_tokens - done.usage.cache_read_input_tokens,
|
|
361
|
+
cacheRead: done.usage.cache_read_input_tokens,
|
|
362
|
+
cacheWrite: done.usage.cache_creation_input_tokens,
|
|
363
|
+
},
|
|
364
|
+
outputTokens: {
|
|
365
|
+
total: done.usage.output_tokens,
|
|
366
|
+
text: done.usage.output_tokens,
|
|
367
|
+
reasoning: undefined,
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
streamCompleted = true;
|
|
372
|
+
controller.close();
|
|
373
|
+
return;
|
|
374
|
+
} else if (event.type === "error") {
|
|
375
|
+
streamCompleted = true;
|
|
376
|
+
controller.error(new Error((event.data as { message: string }).message));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (!streamCompleted) {
|
|
381
|
+
controller.error(new Error("qgrid stream ended unexpectedly"));
|
|
382
|
+
}
|
|
383
|
+
} catch (e) {
|
|
384
|
+
controller.error(e);
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return { stream };
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export { createQgridLogger } from "./logger";
|
|
395
|
+
export type { QgridLoggerConfig, QgridProviderOptions } from "./index.types";
|
|
396
|
+
export default qgrid;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export type QgridProviderConfig = {
|
|
2
|
+
serverUrl?: string;
|
|
3
|
+
defaultEffort?: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* codex app-server가 지원하는 OpenAI provider options.
|
|
8
|
+
*
|
|
9
|
+
* AI SDK의 openai provider options 중 codex가 처리할 수 있는 subset만 정의.
|
|
10
|
+
* 여기에 없는 옵션(temperature, maxOutputTokens 등)은 codex가 무시합니다.
|
|
11
|
+
*
|
|
12
|
+
* @see https://ai-sdk.dev/providers/ai-sdk-providers/openai#provider-options
|
|
13
|
+
*/
|
|
14
|
+
type QgridOpenAIProviderOptions = {
|
|
15
|
+
/** reasoning 모델의 추론 깊이. 기본값은 qgrid config의 defaultEffort. */
|
|
16
|
+
reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
17
|
+
/** reasoning 모델의 추론 요약 출력 방식. Responses API 전용. */
|
|
18
|
+
reasoningSummary?: "auto" | "concise" | "detailed" | "none";
|
|
19
|
+
/** 응답 텍스트의 상세도. */
|
|
20
|
+
textVerbosity?: "low" | "medium" | "high";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* {@link QgridOpenAIProviderOptions}
|
|
25
|
+
*/
|
|
26
|
+
export type QgridProviderOptions = {
|
|
27
|
+
openai?: QgridOpenAIProviderOptions;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type QgridSupportedModel =
|
|
31
|
+
| "openai/gpt-5.5"
|
|
32
|
+
| "openai/gpt-5.4"
|
|
33
|
+
| "openai/gpt-5.2"
|
|
34
|
+
| "openai/gpt-5.4-mini"
|
|
35
|
+
| "openai/gpt-5.3-codex"
|
|
36
|
+
| "openai/gpt-5.3-codex-spark"
|
|
37
|
+
| "anthropic/claude-haiku-4-5"
|
|
38
|
+
| "anthropic/claude-sonnet-4"
|
|
39
|
+
| "anthropic/claude-sonnet-4-5"
|
|
40
|
+
| "anthropic/claude-sonnet-4-6"
|
|
41
|
+
| "anthropic/claude-sonnet-4-7"
|
|
42
|
+
| "anthropic/claude-opus-4"
|
|
43
|
+
| "anthropic/claude-opus-4-1"
|
|
44
|
+
| "anthropic/claude-opus-4-5"
|
|
45
|
+
| "anthropic/claude-opus-4-6"
|
|
46
|
+
| "anthropic/claude-opus-4-7";
|
|
47
|
+
|
|
48
|
+
// 아래 타입들은 Qgrid에서 생성된 type을 그대로 가져와서 사용합니다.
|
|
49
|
+
export type QueryOutput = {
|
|
50
|
+
text: string;
|
|
51
|
+
content?: Array<
|
|
52
|
+
| { type: "text"; text: string }
|
|
53
|
+
| { type: "tool-call"; toolCallId: string; toolName: string; input: string }
|
|
54
|
+
>;
|
|
55
|
+
finishReason?: "stop" | "tool-calls";
|
|
56
|
+
model: string;
|
|
57
|
+
tokenName?: string;
|
|
58
|
+
usage: {
|
|
59
|
+
input_tokens: number;
|
|
60
|
+
output_tokens: number;
|
|
61
|
+
cache_creation_input_tokens: number;
|
|
62
|
+
cache_read_input_tokens: number;
|
|
63
|
+
};
|
|
64
|
+
durationMs: number;
|
|
65
|
+
costUsd: number;
|
|
66
|
+
runContext?: { requestLogId: number };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type CreateRunInput = {
|
|
70
|
+
userPrompt: string;
|
|
71
|
+
systemPrompt?: string;
|
|
72
|
+
modelName?: string;
|
|
73
|
+
effort?: string;
|
|
74
|
+
projectName?: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type AppendStepInput = {
|
|
78
|
+
requestLogId: number;
|
|
79
|
+
stepIndex: number;
|
|
80
|
+
type: "generate" | "tool_call";
|
|
81
|
+
inputTokens?: number;
|
|
82
|
+
outputTokens?: number;
|
|
83
|
+
cacheReadTokens?: number;
|
|
84
|
+
cacheCreationTokens?: number;
|
|
85
|
+
durationMs?: number;
|
|
86
|
+
finishReason?: string;
|
|
87
|
+
reasoningText?: string;
|
|
88
|
+
reasoningTokens?: number;
|
|
89
|
+
toolCallIndex?: number;
|
|
90
|
+
toolCallId?: string;
|
|
91
|
+
toolName?: string;
|
|
92
|
+
toolArgs?: string;
|
|
93
|
+
toolResult?: string;
|
|
94
|
+
toolDurationMs?: number;
|
|
95
|
+
error?: string;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type QgridLoggerConfig = {
|
|
99
|
+
serverUrl: string;
|
|
100
|
+
projectName?: string;
|
|
101
|
+
tokenName?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Fallback timeout for runs that receive onStart but never receive onFinish.
|
|
104
|
+
* AI SDK TelemetryIntegration does not expose an error hook, so provider
|
|
105
|
+
* failures before a final step can otherwise leave request logs running.
|
|
106
|
+
*
|
|
107
|
+
* Set to 0 to disable. Defaults to 30 minutes, or the AI SDK total timeout
|
|
108
|
+
* plus a short grace period when one is provided.
|
|
109
|
+
*/
|
|
110
|
+
staleRunTimeoutMs?: number;
|
|
111
|
+
/**
|
|
112
|
+
* Receives qgrid logging failures. When reusing one logger integration across
|
|
113
|
+
* overlapping AI SDK calls, pass a unique `metadata.qgridRunId` per call so
|
|
114
|
+
* lifecycle events can be attributed to the correct run.
|
|
115
|
+
*/
|
|
116
|
+
onLogError?: (error: Error) => void;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export type FinishRunInput = {
|
|
120
|
+
requestLogId: number;
|
|
121
|
+
status: "succeeded" | "error" | "aborted";
|
|
122
|
+
response?: string;
|
|
123
|
+
tokenName?: string;
|
|
124
|
+
totalInputTokens?: number;
|
|
125
|
+
totalOutputTokens?: number;
|
|
126
|
+
totalCacheReadTokens?: number;
|
|
127
|
+
totalCacheCreationTokens?: number;
|
|
128
|
+
totalDurationMs?: number;
|
|
129
|
+
history?: string;
|
|
130
|
+
errorMessage?: string;
|
|
131
|
+
};
|