@cartanova/qgrid-ai-sdk 0.1.0 → 2.0.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/src/logger.ts DELETED
@@ -1,364 +0,0 @@
1
- import { type TelemetryIntegration, type TelemetrySettings } from "ai";
2
-
3
- import { type QgridLoggerConfig } from "./index.types";
4
- import {
5
- appendStep,
6
- createRun,
7
- extractSystemPrompt,
8
- extractUserPrompt,
9
- finishRun,
10
- getErrorMessage,
11
- getRecord,
12
- safeStringify,
13
- serializeHistory,
14
- } from "./utils";
15
-
16
- type PendingToolCall = {
17
- stepIndex: number;
18
- toolCallIndex: number;
19
- toolCallId: string;
20
- toolName: string;
21
- toolArgs: string;
22
- };
23
-
24
- type RunState = {
25
- requestLogId: number;
26
- pendingSteps: Promise<unknown>[];
27
- pendingToolCalls: PendingToolCall[];
28
- startTime: number;
29
- toolDurations: Map<string, number>;
30
- history?: string;
31
- watchdog?: ReturnType<typeof setTimeout>;
32
- cleanupAbortListener?: () => void;
33
- finishing: boolean;
34
- };
35
-
36
- const DEFAULT_RUN_KEY = "__qgrid_default_run__";
37
- const DEFAULT_STALE_RUN_TIMEOUT_MS = 30 * 60 * 1000;
38
- const STALE_RUN_GRACE_MS = 5000;
39
-
40
- function timedKeySet() {
41
- const keys = new Set<string>();
42
- const timers = new Map<string, ReturnType<typeof setTimeout>>();
43
- return {
44
- has: (k: string) => keys.has(k),
45
- add(k: string, ttlMs: number) {
46
- keys.add(k);
47
- const existing = timers.get(k);
48
- if (existing) clearTimeout(existing);
49
- const t = setTimeout(() => {
50
- keys.delete(k);
51
- timers.delete(k);
52
- }, ttlMs);
53
- t.unref?.();
54
- timers.set(k, t);
55
- },
56
- remove(k: string) {
57
- keys.delete(k);
58
- const t = timers.get(k);
59
- if (t) clearTimeout(t);
60
- timers.delete(k);
61
- },
62
- };
63
- }
64
-
65
- export function createQgridLogger(config: QgridLoggerConfig): TelemetrySettings {
66
- const runs = new Map<string, RunState>();
67
- const keyTtl =
68
- typeof config.staleRunTimeoutMs === "number" && config.staleRunTimeoutMs > 0
69
- ? config.staleRunTimeoutMs
70
- : DEFAULT_STALE_RUN_TIMEOUT_MS;
71
-
72
- const suppressedQgrid = timedKeySet();
73
- const quarantined = timedKeySet();
74
-
75
- const finalizeRun = async (
76
- runKey: string,
77
- result: {
78
- status: "succeeded" | "error" | "aborted";
79
- response?: string;
80
- errorMessage?: string;
81
- totalUsage?: {
82
- inputTokens?: number;
83
- outputTokens?: number;
84
- inputTokenDetails?: { cacheReadTokens?: number; cacheWriteTokens?: number };
85
- };
86
- },
87
- ) => {
88
- const run = runs.get(runKey);
89
- if (!run || run.finishing) return;
90
- run.finishing = true;
91
- runs.delete(runKey);
92
- if (run.watchdog) clearTimeout(run.watchdog);
93
- run.cleanupAbortListener?.();
94
-
95
- for (const pending of run.pendingToolCalls) {
96
- run.pendingSteps.push(
97
- appendStep(config.serverUrl, {
98
- requestLogId: run.requestLogId,
99
- stepIndex: pending.stepIndex,
100
- type: "tool_call",
101
- toolCallIndex: pending.toolCallIndex,
102
- toolCallId: pending.toolCallId,
103
- toolName: pending.toolName,
104
- toolArgs: pending.toolArgs,
105
- toolDurationMs: run.toolDurations.get(pending.toolCallId),
106
- }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e)))),
107
- );
108
- }
109
- run.pendingToolCalls = [];
110
-
111
- await Promise.allSettled(run.pendingSteps);
112
- await finishRun(config.serverUrl, {
113
- requestLogId: run.requestLogId,
114
- status: result.status,
115
- response: result.response,
116
- tokenName: config.tokenName ?? "external",
117
- totalInputTokens: result.totalUsage?.inputTokens ?? 0,
118
- totalOutputTokens: result.totalUsage?.outputTokens ?? 0,
119
- totalCacheReadTokens: result.totalUsage?.inputTokenDetails?.cacheReadTokens ?? 0,
120
- totalCacheCreationTokens: result.totalUsage?.inputTokenDetails?.cacheWriteTokens ?? 0,
121
- totalDurationMs: Date.now() - run.startTime,
122
- history: run.history,
123
- ...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
124
- }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e))));
125
- };
126
-
127
- let autoRunIdCounter = 0;
128
-
129
- const resolveRunKey = (event: {
130
- metadata?: Record<string, unknown>;
131
- functionId?: string;
132
- }): string => {
133
- const qgridRunId = event.metadata?.qgridRunId;
134
- if (typeof qgridRunId === "string" && qgridRunId.length > 0) return `qgridRunId:${qgridRunId}`;
135
- if (typeof event.functionId === "string" && event.functionId.length > 0)
136
- return `functionId:${event.functionId}`;
137
- return DEFAULT_RUN_KEY;
138
- };
139
-
140
- const integration: TelemetryIntegration = {
141
- async onStart(event) {
142
- // qgridRunId/functionId가 없으면 자동 생성 → 병렬 호출 시 run 자동 분리
143
- // metadata 객체 참조가 같은 generation의 모든 hook에서 공유되므로 이후 hook에서도 동일 key
144
- if (!event.metadata?.qgridRunId && !event.functionId && event.metadata) {
145
- event.metadata.qgridRunId = `auto-${++autoRunIdCounter}`;
146
- }
147
- const runKey = resolveRunKey(event);
148
- if (quarantined.has(runKey)) {
149
- config.onLogError?.(
150
- new Error("createQgridLogger: telemetry key is quarantined after overlap"),
151
- );
152
- return;
153
- }
154
-
155
- if (event.model.provider === "qgrid") {
156
- suppressedQgrid.add(runKey, keyTtl);
157
- return;
158
- }
159
-
160
- if (runs.has(runKey)) {
161
- const msg =
162
- "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.";
163
- await finalizeRun(runKey, { status: "error", errorMessage: msg });
164
- quarantined.add(runKey, keyTtl);
165
- config.onLogError?.(new Error(msg));
166
- return;
167
- }
168
-
169
- try {
170
- const messages = event.messages ?? (Array.isArray(event.prompt) ? event.prompt : undefined);
171
- const result = await createRun(config.serverUrl, {
172
- userPrompt: extractUserPrompt(event.prompt, messages),
173
- systemPrompt: extractSystemPrompt(event.system),
174
- modelName: event.model.modelId,
175
- projectName: config.projectName,
176
- });
177
-
178
- // watchdog timeout
179
- let watchdogTimeout = DEFAULT_STALE_RUN_TIMEOUT_MS;
180
- if (typeof config.staleRunTimeoutMs === "number")
181
- watchdogTimeout = config.staleRunTimeoutMs;
182
- else if (typeof event.timeout === "number" && event.timeout > 0)
183
- watchdogTimeout = event.timeout + STALE_RUN_GRACE_MS;
184
- else {
185
- const rec = getRecord(event.timeout);
186
- const totalMs = rec?.totalMs;
187
- if (typeof totalMs === "number" && totalMs > 0)
188
- watchdogTimeout = totalMs + STALE_RUN_GRACE_MS;
189
- }
190
-
191
- let watchdog: ReturnType<typeof setTimeout> | undefined;
192
- if (watchdogTimeout > 0) {
193
- watchdog = setTimeout(() => {
194
- void finalizeRun(runKey, {
195
- status: "error",
196
- errorMessage: "AI SDK generation ended before onFinish was emitted",
197
- });
198
- }, watchdogTimeout);
199
- watchdog.unref?.();
200
- }
201
-
202
- // abort listener
203
- let cleanupAbortListener: (() => void) | undefined;
204
- const signal = event.abortSignal;
205
- if (signal) {
206
- const onAbort = () => {
207
- void finalizeRun(runKey, {
208
- status: "aborted",
209
- errorMessage: getErrorMessage(signal.reason),
210
- });
211
- };
212
- if (signal.aborted) queueMicrotask(onAbort);
213
- else {
214
- signal.addEventListener("abort", onAbort, { once: true });
215
- cleanupAbortListener = () => signal.removeEventListener("abort", onAbort);
216
- }
217
- }
218
-
219
- runs.set(runKey, {
220
- requestLogId: result.requestLogId,
221
- pendingSteps: [],
222
- pendingToolCalls: [],
223
- startTime: Date.now(),
224
- toolDurations: new Map(),
225
- history: serializeHistory(messages),
226
- watchdog,
227
- cleanupAbortListener,
228
- finishing: false,
229
- });
230
- } catch (e) {
231
- config.onLogError?.(e instanceof Error ? e : new Error(String(e)));
232
- }
233
- },
234
-
235
- onToolCallFinish(event) {
236
- const runKey = resolveRunKey(event);
237
- if (suppressedQgrid.has(runKey) || quarantined.has(runKey)) return;
238
- const run = runs.get(runKey);
239
- if (!run) return;
240
- run.toolDurations.set(event.toolCall.toolCallId, Math.round(event.durationMs));
241
- },
242
-
243
- onStepFinish(event) {
244
- const runKey = resolveRunKey(event);
245
- if (suppressedQgrid.has(runKey) || quarantined.has(runKey)) return;
246
- const run = runs.get(runKey);
247
- if (!run) return;
248
-
249
- const { content, usage, reasoningText, finishReason, stepNumber } = event;
250
-
251
- // 이전 step의 pending tool-call 매칭
252
- const remainingPending: PendingToolCall[] = [];
253
- for (const pending of run.pendingToolCalls) {
254
- const tr = content.find(
255
- (p) => p.type === "tool-result" && p.toolCallId === pending.toolCallId,
256
- );
257
- const te = content.find(
258
- (p) => p.type === "tool-error" && p.toolCallId === pending.toolCallId,
259
- );
260
- // tool call 에러도 step으로 간주
261
- if (tr || te) {
262
- run.pendingSteps.push(
263
- appendStep(config.serverUrl, {
264
- requestLogId: run.requestLogId,
265
- stepIndex: pending.stepIndex,
266
- type: "tool_call",
267
- toolCallIndex: pending.toolCallIndex,
268
- toolCallId: pending.toolCallId,
269
- toolName: pending.toolName,
270
- toolArgs: pending.toolArgs,
271
- toolResult: tr && "output" in tr ? safeStringify(tr.output) : undefined,
272
- toolDurationMs: run.toolDurations.get(pending.toolCallId),
273
- error: te && "error" in te ? safeStringify(te.error) : undefined,
274
- }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e)))),
275
- );
276
- run.toolDurations.delete(pending.toolCallId);
277
- } else {
278
- remainingPending.push(pending);
279
- }
280
- }
281
- run.pendingToolCalls = remainingPending;
282
-
283
- // generate step
284
- run.pendingSteps.push(
285
- appendStep(config.serverUrl, {
286
- requestLogId: run.requestLogId,
287
- stepIndex: stepNumber,
288
- type: "generate",
289
- inputTokens: usage.inputTokens ?? 0,
290
- outputTokens: usage.outputTokens ?? 0,
291
- cacheReadTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
292
- cacheCreationTokens: usage.inputTokenDetails?.cacheWriteTokens ?? 0,
293
- finishReason,
294
- reasoningText:
295
- typeof reasoningText === "string" && reasoningText.length > 0
296
- ? reasoningText
297
- : undefined,
298
- reasoningTokens: usage.outputTokenDetails?.reasoningTokens,
299
- }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e)))),
300
- );
301
-
302
- // 이번 step의 새 tool-call
303
- const toolCalls = content.filter(
304
- (p): p is Extract<(typeof content)[number], { type: "tool-call" }> =>
305
- p.type === "tool-call",
306
- );
307
- for (const [i, tc] of toolCalls.entries()) {
308
- const tr = content.find((p) => p.type === "tool-result" && p.toolCallId === tc.toolCallId);
309
- const te = content.find((p) => p.type === "tool-error" && p.toolCallId === tc.toolCallId);
310
- if (tr || te) {
311
- run.pendingSteps.push(
312
- appendStep(config.serverUrl, {
313
- requestLogId: run.requestLogId,
314
- stepIndex: stepNumber,
315
- type: "tool_call",
316
- toolCallIndex: i,
317
- toolCallId: tc.toolCallId,
318
- toolName: tc.toolName,
319
- toolArgs: safeStringify(tc.input),
320
- toolResult: tr && "output" in tr ? safeStringify(tr.output) : undefined,
321
- toolDurationMs: run.toolDurations.get(tc.toolCallId),
322
- error: te && "error" in te ? safeStringify(te.error) : undefined,
323
- }).catch((e) => config.onLogError?.(e instanceof Error ? e : new Error(String(e)))),
324
- );
325
- run.toolDurations.delete(tc.toolCallId);
326
- } else {
327
- run.pendingToolCalls.push({
328
- stepIndex: stepNumber,
329
- toolCallIndex: i,
330
- toolCallId: tc.toolCallId,
331
- toolName: tc.toolName,
332
- toolArgs: safeStringify(tc.input),
333
- });
334
- }
335
- }
336
- },
337
-
338
- async onFinish(event) {
339
- const runKey = resolveRunKey(event);
340
-
341
- if (suppressedQgrid.has(runKey)) {
342
- suppressedQgrid.remove(runKey);
343
- return;
344
- }
345
- if (quarantined.has(runKey)) return;
346
-
347
- const run = runs.get(runKey);
348
- if (!run) return;
349
-
350
- const status = event.finishReason === "error" ? "error" : "succeeded";
351
-
352
- await finalizeRun(runKey, {
353
- status,
354
- response: event.text,
355
- totalUsage: event.totalUsage,
356
- ...(status === "error" && "error" in event
357
- ? { errorMessage: getErrorMessage(event.error) }
358
- : {}),
359
- });
360
- },
361
- };
362
-
363
- return { isEnabled: true, integrations: [integration] };
364
- }
package/src/utils.ts DELETED
@@ -1,305 +0,0 @@
1
- import { type LanguageModelV3FunctionTool, type LanguageModelV3Message } from "@ai-sdk/provider";
2
-
3
- import { type AppendStepInput, type CreateRunInput, type FinishRunInput } from "./index.types";
4
-
5
- // API helpers
6
- export async function createRun(
7
- serverUrl: string,
8
- body: CreateRunInput,
9
- ): Promise<{ requestLogId: number }> {
10
- const res = await fetch(`${serverUrl}/api/qgrid/createRun`, {
11
- method: "POST",
12
- headers: { "Content-Type": "application/json" },
13
- body: JSON.stringify({ input: body }),
14
- });
15
- return res.json() as Promise<{ requestLogId: number }>;
16
- }
17
-
18
- export async function appendStep(
19
- serverUrl: string,
20
- body: AppendStepInput,
21
- ): Promise<{ stepId: number }> {
22
- const res = await fetch(`${serverUrl}/api/qgrid/appendStep`, {
23
- method: "POST",
24
- headers: { "Content-Type": "application/json" },
25
- body: JSON.stringify({ input: body }),
26
- });
27
- return res.json() as Promise<{ stepId: number }>;
28
- }
29
-
30
- export async function finishRun(serverUrl: string, body: FinishRunInput): Promise<{ ok: boolean }> {
31
- const res = await fetch(`${serverUrl}/api/qgrid/finishRun`, {
32
- method: "POST",
33
- headers: { "Content-Type": "application/json" },
34
- body: JSON.stringify({ input: body }),
35
- });
36
- return res.json() as Promise<{ ok: boolean }>;
37
- }
38
-
39
- // Tool helpers
40
- export function toQgridTool(tool: LanguageModelV3FunctionTool): {
41
- name: string;
42
- description?: string;
43
- inputSchema: unknown;
44
- } {
45
- const source = tool as LanguageModelV3FunctionTool & {
46
- inputSchema?: unknown;
47
- parameters?: unknown;
48
- };
49
- return {
50
- name: tool.name,
51
- description: tool.description,
52
- inputSchema: source.inputSchema ?? source.parameters ?? {},
53
- };
54
- }
55
-
56
- type ToolCallWithResult = {
57
- callId: string;
58
- toolName: string;
59
- args: string;
60
- result: string;
61
- };
62
-
63
- export function extractToolResultsFromHistory(
64
- messages: LanguageModelV3Message[],
65
- ): ToolCallWithResult[] {
66
- const calls = new Map<string, { toolName: string; args: string }>();
67
- const results = new Map<string, string>();
68
- for (const msg of messages) {
69
- if (msg.role === "assistant") {
70
- for (const part of msg.content) {
71
- if ("type" in part && part.type === "tool-call") {
72
- calls.set(part.toolCallId, {
73
- toolName: part.toolName,
74
- args: typeof part.input === "string" ? part.input : JSON.stringify(part.input),
75
- });
76
- }
77
- }
78
- } else if (msg.role === "tool") {
79
- for (const part of msg.content) {
80
- if ("type" in part && part.type === "tool-result") {
81
- const id = "toolCallId" in part ? part.toolCallId : "";
82
- const output = part.output;
83
- const text =
84
- "value" in output
85
- ? typeof output.value === "string"
86
- ? output.value
87
- : JSON.stringify(output.value)
88
- : JSON.stringify(output);
89
- results.set(id, text);
90
- }
91
- }
92
- }
93
- }
94
-
95
- const out: ToolCallWithResult[] = [];
96
- for (const [callId, call] of calls) {
97
- if (results.has(callId)) {
98
- out.push({ callId, toolName: call.toolName, args: call.args, result: results.get(callId)! });
99
- }
100
- }
101
- return out;
102
- }
103
-
104
- // SSE parser
105
- export type SSEEvent = { type: string; data: Record<string, unknown> };
106
-
107
- export async function* parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<SSEEvent> {
108
- const reader = body.getReader();
109
- const decoder = new TextDecoder();
110
- let buffer = "";
111
- let eventType = "";
112
-
113
- for (;;) {
114
- const { done, value } = await reader.read();
115
- if (done) break;
116
- buffer += decoder.decode(value, { stream: true });
117
-
118
- const lines = buffer.split("\n");
119
- buffer = lines.pop() ?? "";
120
-
121
- for (const line of lines) {
122
- if (line.startsWith("event: ")) {
123
- eventType = line.slice(7).trim();
124
- } else if (line.startsWith("data: ")) {
125
- const raw = line.slice(6);
126
- try {
127
- yield { type: eventType || "message", data: JSON.parse(raw) };
128
- } catch {}
129
- eventType = "";
130
- }
131
- }
132
- }
133
- }
134
-
135
- // Prompt helpers
136
- export function extractPromptAndHistory(messages: LanguageModelV3Message[]): {
137
- prompt: string;
138
- system: string | undefined;
139
- history: unknown[];
140
- } {
141
- let system: string | undefined;
142
- const nonSystem: LanguageModelV3Message[] = [];
143
-
144
- for (const msg of messages) {
145
- if (msg.role === "system") {
146
- system = extractTextFromContent(msg.content);
147
- } else {
148
- nonSystem.push(msg);
149
- }
150
- }
151
-
152
- if (nonSystem.length === 0) return { prompt: "", system, history: [] };
153
- if (nonSystem.length === 1 && nonSystem[0].role === "user") {
154
- return { prompt: extractTextFromContent(nonSystem[0].content), system, history: [] };
155
- }
156
-
157
- const last = nonSystem[nonSystem.length - 1];
158
- const prompt = last.role === "user" ? extractTextFromContent(last.content) : "";
159
- const historyEnd = last.role === "user" ? nonSystem.length - 1 : nonSystem.length;
160
-
161
- const history: unknown[] = [];
162
- for (let i = 0; i < historyEnd; i++) {
163
- const msg = nonSystem[i];
164
- if (msg.role === "user") {
165
- history.push({
166
- type: "message",
167
- role: "user",
168
- content: [{ type: "input_text", text: extractTextFromContent(msg.content) }],
169
- });
170
- } else if (msg.role === "assistant") {
171
- for (const part of msg.content) {
172
- if ("text" in part && typeof part.text === "string") {
173
- history.push({
174
- type: "message",
175
- role: "assistant",
176
- content: [{ type: "output_text", text: part.text }],
177
- });
178
- } else if ("toolName" in part && part.type === "tool-call") {
179
- history.push({
180
- type: "function_call",
181
- name: part.toolName,
182
- arguments: typeof part.input === "string" ? part.input : JSON.stringify(part.input),
183
- call_id: part.toolCallId,
184
- });
185
- }
186
- }
187
- } else if (msg.role === "tool") {
188
- for (const part of msg.content) {
189
- if ("toolName" in part && part.type === "tool-result") {
190
- const output = part.output;
191
- const text =
192
- "value" in output
193
- ? typeof output.value === "string"
194
- ? output.value
195
- : JSON.stringify(output.value)
196
- : JSON.stringify(output);
197
- history.push({
198
- type: "function_call_output",
199
- call_id: ("toolCallId" in part ? part.toolCallId : "") ?? "",
200
- output: text,
201
- });
202
- }
203
- }
204
- }
205
- }
206
-
207
- return { prompt, system, history };
208
- }
209
-
210
- function extractTextFromContent(content: LanguageModelV3Message["content"]): string {
211
- if (typeof content === "string") return content;
212
- const parts: string[] = [];
213
- for (const part of content) {
214
- if ("text" in part && typeof part.text === "string") parts.push(part.text);
215
- }
216
- return parts.join("\n");
217
- }
218
-
219
- // --- shared helpers (used by both index.ts and logger.ts) ---
220
-
221
- export function safeStringify(value: unknown): string {
222
- try {
223
- const json = JSON.stringify(value);
224
- return json === undefined ? String(value) : json;
225
- } catch {
226
- return String(value);
227
- }
228
- }
229
-
230
- export function getRecord(value: unknown): Record<string, unknown> | undefined {
231
- return value && typeof value === "object" && !Array.isArray(value)
232
- ? (value as Record<string, unknown>)
233
- : undefined;
234
- }
235
-
236
- export function extractTextContent(content: unknown): string {
237
- if (typeof content === "string") return content;
238
- const parts: string[] = [];
239
- if (Array.isArray(content)) {
240
- for (const part of content) {
241
- if (part && typeof part === "object" && "type" in part) {
242
- if (part.type === "text" && "text" in part && typeof part.text === "string") {
243
- parts.push(part.text);
244
- }
245
- }
246
- }
247
- }
248
- return parts.join("\n");
249
- }
250
-
251
- export function getErrorMessage(value: unknown): string {
252
- if (value instanceof Error) return String(value);
253
- if (value === undefined || value === null) return "unknown error";
254
- return String(value);
255
- }
256
-
257
- export function extractUserPrompt(prompt: unknown, messages: unknown): string {
258
- if (typeof prompt === "string") return prompt;
259
- const messageList = Array.isArray(messages) ? messages : Array.isArray(prompt) ? prompt : [];
260
- for (let i = messageList.length - 1; i >= 0; i--) {
261
- const msg = getRecord(messageList[i]);
262
- if (msg?.role === "user") {
263
- return extractTextContent(msg.content);
264
- }
265
- }
266
- return "";
267
- }
268
-
269
- export function extractSystemPrompt(system: unknown): string | undefined {
270
- if (typeof system === "string") return system;
271
- if (system && typeof system === "object" && "content" in system) {
272
- const content = (system as { content: unknown }).content;
273
- if (typeof content === "string") return content;
274
- }
275
- return undefined;
276
- }
277
-
278
- export function serializeHistory(messages: unknown): string | undefined {
279
- if (!Array.isArray(messages) || messages.length === 0) return undefined;
280
- const lastRecord = getRecord(messages[messages.length - 1]);
281
- const sliced = lastRecord?.role === "user" ? messages.slice(0, -1) : messages;
282
- const history: Array<Record<string, unknown>> = [];
283
- for (const msg of sliced) {
284
- const record = getRecord(msg);
285
- if (record?.role !== "user" && record?.role !== "assistant") continue;
286
- const text = extractTextContent(record.content);
287
- if (text.length === 0) continue;
288
- history.push({
289
- type: "message",
290
- role: record.role,
291
- content: [{ type: record.role === "user" ? "input_text" : "output_text", text }],
292
- });
293
- }
294
- if (history.length === 0) return undefined;
295
- return safeStringify(history);
296
- }
297
-
298
- export function filterHistoryForStorage(history: unknown[]): string | undefined {
299
- const filtered = history.filter((item) => {
300
- if (!item || typeof item !== "object") return false;
301
- const record = item as Record<string, unknown>;
302
- return record.type === "message" && (record.role === "user" || record.role === "assistant");
303
- });
304
- return filtered.length > 0 ? JSON.stringify(filtered) : undefined;
305
- }
package/tsconfig.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "skipLibCheck": true,
9
- "outDir": "dist",
10
- "rootDir": "src",
11
- "declaration": true,
12
- "types": ["node"]
13
- },
14
- "include": ["src"]
15
- }
package/tsdown.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from "tsdown";
2
-
3
- export default defineConfig({
4
- entry: ["src/index.ts"],
5
- dts: true,
6
- clean: true,
7
- format: "esm",
8
- external: ["ai", "@ai-sdk/provider", "@ai-sdk/provider-utils"],
9
- });