@cartanova/qgrid-ai-sdk 2.4.7 → 2.4.9
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 +38 -1
- package/README.md +38 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.js +110 -40
- package/package.json +4 -4
package/README.ko.md
CHANGED
|
@@ -139,6 +139,9 @@ AI SDK가 tool 실행을 관리하고, qgrid는 각 턴의 LLM 호출만 담당
|
|
|
139
139
|
### Provider Options
|
|
140
140
|
|
|
141
141
|
qgrid 전용 옵션은 전부 `providerOptions.qgrid` 네임스페이스로 전달합니다. (`providerOptions.openai`가 아닙니다)
|
|
142
|
+
AI SDK는 바깥 `providerOptions`를 범용 JSON record로 타입 선언하므로 qgrid 옵션을 자동 추론하지 못합니다.
|
|
143
|
+
따라서 공개 타입 `QgridProviderOptions`를 중첩된 `qgrid` 값에 `satisfies`로 적용하세요. literal 추론을
|
|
144
|
+
유지하면서 qgrid 옵션의 오타와 잘못된 값을 컴파일 타임에 잡을 수 있습니다.
|
|
142
145
|
|
|
143
146
|
```typescript
|
|
144
147
|
import { generateText } from "ai";
|
|
@@ -159,6 +162,7 @@ const { text } = await generateText({
|
|
|
159
162
|
|
|
160
163
|
| 옵션 | 값 | 적용 범위 | 설명 |
|
|
161
164
|
|---|---|---|---|
|
|
165
|
+
| `logger` | `boolean` | 공통 | qgrid request log 저장 여부. 기본값은 `true`. `false`로 설정해도 client tool 실행과 multi-step 연결은 계속 동작 |
|
|
162
166
|
| `sessionKey` | `string` | OpenAI 전용 | 멀티턴 대화 식별자. 같은 key는 같은 codex thread로 라우팅되어 prompt cache 적중 ([아래](#멀티턴-prompt-cache-sessionkey) 참조) |
|
|
163
167
|
| `effort` | `"none"` \| `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` \| `"max"` | 공통 (지원 값은 모델별 상이, 예: `"max"`는 GPT-5.6+) | reasoning 모델의 추론 깊이. 기본값은 config의 `defaultEffort` (`"low"`) |
|
|
164
168
|
| `verbosity` | `"low"` \| `"medium"` \| `"high"` | OpenAI 전용 | 응답 텍스트의 상세도 |
|
|
@@ -166,7 +170,7 @@ const { text } = await generateText({
|
|
|
166
170
|
| `serviceTier` | `string` | OpenAI 전용 | OpenAI/codex service tier |
|
|
167
171
|
| `imageGeneration` | `boolean` | OpenAI 전용, non-stream | codex 내장 `image_generation` tool 활성화 ([아래](#image-generation) 참조) |
|
|
168
172
|
| `imageGenerationOptions` | `{ quality?, size? }` | OpenAI 전용 | 이미지 품질/크기 힌트. `quality: "low" \| "medium" \| "high"`, `size: "1024x1024" \| "1024x1536" \| "1536x1024"` (기본: `medium` / `1536x1024`) |
|
|
169
|
-
| `fallbackModels` | `string[]` | 예약 | 향후 서버 fallback routing용 예약 필드. 현재 동작하지
|
|
173
|
+
| `fallbackModels` | `string[]` | 예약 | 향후 qgrid 서버 fallback routing용 예약 필드. 현재 동작하지 않으며 Claude Code의 Fable refusal fallback과 무관 |
|
|
170
174
|
|
|
171
175
|
### 멀티턴 prompt cache (sessionKey)
|
|
172
176
|
|
|
@@ -260,6 +264,29 @@ createQgridLogger({
|
|
|
260
264
|
|
|
261
265
|
모든 설정은 optional (serverUrl 제외). 기본값이 있으므로 `serverUrl`만 넣으면 동작합니다.
|
|
262
266
|
|
|
267
|
+
특정 generation을 request log 저장에서 제외하려면 `providerOptions.qgrid.logger`를
|
|
268
|
+
`false`로 설정하세요. qgrid provider 호출과 `createQgridLogger`가 관찰하는 외부 provider
|
|
269
|
+
호출에 모두 적용되며, tool 실행과 multi-step 연결은 계속 정상 동작합니다.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { type QgridProviderOptions } from "@cartanova/qgrid-ai-sdk";
|
|
273
|
+
|
|
274
|
+
const { text } = await generateText({
|
|
275
|
+
model: google("gemini-3-flash"),
|
|
276
|
+
prompt: "이 요청은 저장하지 마",
|
|
277
|
+
providerOptions: {
|
|
278
|
+
qgrid: { logger: false } satisfies QgridProviderOptions,
|
|
279
|
+
},
|
|
280
|
+
experimental_telemetry: createQgridLogger({ serverUrl: "http://localhost:44900" }),
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
외부 provider request log의 모델 이름은 `provider/modelId` 형식으로 저장됩니다. provider가
|
|
285
|
+
AI SDK response metadata로 실제 serving 모델을 다르게 보고하면 step과 final log에는
|
|
286
|
+
관찰된 serving 모델을 저장하고 requested model은 별도로 유지합니다. AI SDK runtime의
|
|
287
|
+
`response.modelId`는 변경하지 않습니다. `openai.responses`는 `openai`로,
|
|
288
|
+
`anthropic.messages`는 `anthropic`으로 적재하는 등 AI SDK adapter suffix는 base provider로 정규화합니다.
|
|
289
|
+
|
|
263
290
|
### qgrid provider와 함께 사용
|
|
264
291
|
|
|
265
292
|
`qgrid()` provider는 자체 lifecycle이 있으므로 logger가 자동으로 suppress됩니다. 같은 코드에서 qgrid provider와 다른 provider를 섞어 써도 이중 기록되지 않습니다.
|
|
@@ -279,6 +306,7 @@ type QgridSupportedModel =
|
|
|
279
306
|
| "openai/gpt-5.3-codex"
|
|
280
307
|
| "openai/gpt-5.3-codex-spark"
|
|
281
308
|
// Anthropic
|
|
309
|
+
| "anthropic/claude-fable-5"
|
|
282
310
|
| "anthropic/claude-haiku-4-5"
|
|
283
311
|
| "anthropic/claude-sonnet-4"
|
|
284
312
|
| "anthropic/claude-sonnet-4-5"
|
|
@@ -291,6 +319,7 @@ type QgridSupportedModel =
|
|
|
291
319
|
| "anthropic/claude-opus-4-6"
|
|
292
320
|
| "anthropic/claude-opus-4-7"
|
|
293
321
|
| "anthropic/claude-opus-4-8"
|
|
322
|
+
| "anthropic/claude-opus-5"
|
|
294
323
|
```
|
|
295
324
|
|
|
296
325
|
### GPT-5.6 사양
|
|
@@ -303,6 +332,14 @@ type QgridSupportedModel =
|
|
|
303
332
|
|
|
304
333
|
세 모델 모두 `max` reasoning effort를 지원합니다. OpenAI native API 사양은 1.05M context / 128K 최대 출력이지만, qgrid는 codex app-server 구독 경로로 실행되어 세 모델 모두 context window가 372K(95% effective, 실사용 입력 약 353K)로 보고되며 설정으로 올릴 수 없습니다. 입력이 272K tokens를 넘으면 요청 전체에 input 2x, output 1.5x 장문 컨텍스트 할증이 적용되며, cache write는 uncached input 단가의 1.25x입니다.
|
|
305
334
|
|
|
335
|
+
`anthropic/claude-fable-5`는 1M context와 128K 최대 출력을 지원합니다. 1M tokens당 표준 단가는 input $10, cache read $1, 5분 cache write $12.50, 1시간 cache write $20, output $50입니다. qgrid는 Claude 응답의 5분/1시간 cache creation breakdown을 보존해 TTL별 단가를 각각 적용하며, breakdown이 없는 구버전 응답에서만 Claude Code가 subscription OAuth 경로에 자동 적용하는 1시간 TTL 단가로 fallback합니다. Fable은 adaptive thinking이 항상 켜져 있어야 하므로 qgrid는 이 모델의 adaptive thinking을 보존합니다.
|
|
336
|
+
|
|
337
|
+
`anthropic/claude-opus-5`는 기본 1M context와 128K 최대 출력을 지원합니다. 1M tokens당 단가는 input $5, cache read $0.50, 5분 cache write $6.25, 1시간 cache write $10, output $25입니다. qgrid는 Opus 5의 기본 adaptive thinking 동작을 유지하고 `effort`로 추론 깊이를 조절합니다. 따라서 `xhigh` 또는 `max` effort에서 허용되지 않는 `thinking: disabled` 조합도 만들지 않습니다.
|
|
338
|
+
|
|
339
|
+
Claude Code는 Fable의 safety refusal을 Opus 4.8로 자동 재시도할 수 있습니다. 이 경우 AI SDK 응답의 `response.modelId`와 `providerMetadata.qgrid.model`은 실제 serving 모델인 Opus를 가리킵니다. `providerMetadata.qgrid.requestedModel`은 Fable로 유지되고, `providerMetadata.qgrid.modelFallbacks`에 refusal fallback 이력이 담깁니다. 같은 metadata에서 `costSource`와 5분/1시간 cache-write 토큰 분해도 확인할 수 있습니다.
|
|
340
|
+
|
|
341
|
+
`openai/gpt-5.3-codex-spark`는 아직 token 단가가 확정·공개되지 않은 research preview입니다. 따라서 qgrid는 generic fallback 추정치를 보고하며, 이를 공식 단가로 취급하지 않습니다.
|
|
342
|
+
|
|
306
343
|
## 설정
|
|
307
344
|
|
|
308
345
|
```typescript
|
package/README.md
CHANGED
|
@@ -139,6 +139,9 @@ The AI SDK manages tool execution; qgrid handles only each turn's LLM call.
|
|
|
139
139
|
### Provider Options
|
|
140
140
|
|
|
141
141
|
All qgrid-specific options go under the `providerOptions.qgrid` namespace. (Not `providerOptions.openai`.)
|
|
142
|
+
AI SDK types the outer `providerOptions` as a generic JSON record, so apply the exported
|
|
143
|
+
`QgridProviderOptions` type to the nested `qgrid` value with `satisfies`. This preserves
|
|
144
|
+
literal inference and catches misspelled or invalid qgrid options at compile time.
|
|
142
145
|
|
|
143
146
|
```typescript
|
|
144
147
|
import { generateText } from "ai";
|
|
@@ -159,6 +162,7 @@ const { text } = await generateText({
|
|
|
159
162
|
|
|
160
163
|
| Option | Values | Applies to | Description |
|
|
161
164
|
|---|---|---|---|
|
|
165
|
+
| `logger` | `boolean` | both providers | qgrid request logging. Defaults to `true`; `false` disables request-log persistence for this generation without disabling client tools or multi-step continuation |
|
|
162
166
|
| `sessionKey` | `string` | OpenAI only | Multi-turn conversation identifier. The same key routes to the same codex thread for prompt-cache hits (see [below](#multi-turn-prompt-cache-sessionkey)) |
|
|
163
167
|
| `effort` | `"none"` \| `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` \| `"max"` | both providers (supported values are model-dependent, e.g. `"max"` is GPT-5.6+) | Reasoning depth. Defaults to the config's `defaultEffort` (`"low"`) |
|
|
164
168
|
| `verbosity` | `"low"` \| `"medium"` \| `"high"` | OpenAI only | Response text verbosity |
|
|
@@ -166,7 +170,7 @@ const { text } = await generateText({
|
|
|
166
170
|
| `serviceTier` | `string` | OpenAI only | OpenAI/codex service tier |
|
|
167
171
|
| `imageGeneration` | `boolean` | OpenAI only, non-stream | Enables codex's built-in `image_generation` tool (see [below](#image-generation)) |
|
|
168
172
|
| `imageGenerationOptions` | `{ quality?, size? }` | OpenAI only | Image quality/size hints. `quality: "low" \| "medium" \| "high"`, `size: "1024x1024" \| "1024x1536" \| "1536x1024"` (defaults: `medium` / `1536x1024`) |
|
|
169
|
-
| `fallbackModels` | `string[]` | reserved | Reserved for future server-side fallback routing. Not functional yet |
|
|
173
|
+
| `fallbackModels` | `string[]` | reserved | Reserved for future qgrid server-side fallback routing. Not functional yet and unrelated to Claude Code's Fable refusal fallback |
|
|
170
174
|
|
|
171
175
|
### Multi-turn prompt cache (sessionKey)
|
|
172
176
|
|
|
@@ -260,6 +264,29 @@ createQgridLogger({
|
|
|
260
264
|
|
|
261
265
|
Everything except `serverUrl` is optional. With defaults in place, passing only `serverUrl` works.
|
|
262
266
|
|
|
267
|
+
To opt one generation out of request logging, set `providerOptions.qgrid.logger` to `false`.
|
|
268
|
+
The same option works for qgrid-provider calls and for external-provider calls observed by
|
|
269
|
+
`createQgridLogger`; tool execution and multi-step continuation still work normally.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import { type QgridProviderOptions } from "@cartanova/qgrid-ai-sdk";
|
|
273
|
+
|
|
274
|
+
const { text } = await generateText({
|
|
275
|
+
model: google("gemini-3-flash"),
|
|
276
|
+
prompt: "Do not persist this request",
|
|
277
|
+
providerOptions: {
|
|
278
|
+
qgrid: { logger: false } satisfies QgridProviderOptions,
|
|
279
|
+
},
|
|
280
|
+
experimental_telemetry: createQgridLogger({ serverUrl: "http://localhost:44900" }),
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
External-provider request logs store model names as `provider/modelId`. If the provider reports
|
|
285
|
+
a different served model in the AI SDK response metadata, step and final log rows record that
|
|
286
|
+
observed model while retaining the requested model separately. This does not alter the AI SDK
|
|
287
|
+
runtime `response.modelId`. AI SDK adapter suffixes are normalized to the base provider, for
|
|
288
|
+
example `openai.responses` to `openai` and `anthropic.messages` to `anthropic`.
|
|
289
|
+
|
|
263
290
|
### Using alongside the qgrid provider
|
|
264
291
|
|
|
265
292
|
The `qgrid()` provider has its own lifecycle, so the logger automatically suppresses itself for qgrid calls. Mixing the qgrid provider and other providers in the same code does not double-log.
|
|
@@ -279,6 +306,7 @@ type QgridSupportedModel =
|
|
|
279
306
|
| "openai/gpt-5.3-codex"
|
|
280
307
|
| "openai/gpt-5.3-codex-spark"
|
|
281
308
|
// Anthropic
|
|
309
|
+
| "anthropic/claude-fable-5"
|
|
282
310
|
| "anthropic/claude-haiku-4-5"
|
|
283
311
|
| "anthropic/claude-sonnet-4"
|
|
284
312
|
| "anthropic/claude-sonnet-4-5"
|
|
@@ -291,6 +319,7 @@ type QgridSupportedModel =
|
|
|
291
319
|
| "anthropic/claude-opus-4-6"
|
|
292
320
|
| "anthropic/claude-opus-4-7"
|
|
293
321
|
| "anthropic/claude-opus-4-8"
|
|
322
|
+
| "anthropic/claude-opus-5"
|
|
294
323
|
```
|
|
295
324
|
|
|
296
325
|
### GPT-5.6 specifications
|
|
@@ -303,6 +332,14 @@ type QgridSupportedModel =
|
|
|
303
332
|
|
|
304
333
|
All GPT-5.6 models support reasoning through `max`. The OpenAI native API spec is a 1.05M context window with 128K max output, but qgrid runs on the codex app-server subscription path, where all three models report a 372K context window (95% effective — about 353K of usable input) that cannot be raised by configuration. Prompts over 272K input tokens apply a 2x input and 1.5x output surcharge to the full request; cache writes cost 1.25x the uncached input rate.
|
|
305
334
|
|
|
335
|
+
`anthropic/claude-fable-5` has a 1M context window and 128K max output. Its standard prices per 1M tokens are $10 input, $1 cache read, $12.50 five-minute cache write, $20 one-hour cache write, and $50 output. qgrid preserves Claude's 5m/1h cache-creation breakdown and prices each TTL separately; only legacy responses without that breakdown fall back to the one-hour TTL automatically selected by Claude Code on subscription OAuth. Fable requires always-on adaptive thinking, so qgrid preserves adaptive thinking for this model.
|
|
336
|
+
|
|
337
|
+
`anthropic/claude-opus-5` has a default 1M context window and 128K max output. Its prices per 1M tokens are $5 input, $0.50 cache read, $6.25 five-minute cache write, $10 one-hour cache write, and $25 output. qgrid keeps Opus 5's default adaptive thinking behavior and uses `effort` to control reasoning depth. This also avoids the invalid `thinking: disabled` combination at `xhigh` or `max` effort.
|
|
338
|
+
|
|
339
|
+
Claude Code may automatically retry a Fable safety refusal on Opus 4.8. In that case, the AI SDK response's `response.modelId` and `providerMetadata.qgrid.model` identify Opus as the actual serving model. `providerMetadata.qgrid.requestedModel` remains Fable, and `providerMetadata.qgrid.modelFallbacks` contains the refusal fallback history. The metadata also exposes `costSource` and the 5m/1h cache-write token split.
|
|
340
|
+
|
|
341
|
+
`openai/gpt-5.3-codex-spark` remains a research preview without final published per-token rates. qgrid therefore reports its generic fallback estimate rather than presenting that estimate as official pricing.
|
|
342
|
+
|
|
306
343
|
## Configuration
|
|
307
344
|
|
|
308
345
|
```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { LanguageModelV3 } from "@ai-sdk/provider";
|
|
2
2
|
import { TelemetrySettings } from "ai";
|
|
3
|
-
|
|
4
3
|
//#region src/index.types.d.ts
|
|
5
4
|
type QgridProviderConfig = {
|
|
6
5
|
serverUrl?: string;
|
|
@@ -13,6 +12,11 @@ type QgridProviderConfig = {
|
|
|
13
12
|
* providerOptions.qgrid 로 전달한다.
|
|
14
13
|
*/
|
|
15
14
|
type QgridProviderOptions = {
|
|
15
|
+
/**
|
|
16
|
+
* qgrid request log 저장 여부. 기본값은 true.
|
|
17
|
+
* false여도 client tool 실행과 multi-step 연결은 계속 동작한다.
|
|
18
|
+
*/
|
|
19
|
+
logger?: boolean;
|
|
16
20
|
/**
|
|
17
21
|
* 멀티턴 시 codex thread reuse를 위한 대화 식별자, 호출자가 자기 도메인 ID(예: 게임 세션 ID) 하나만 넘기면
|
|
18
22
|
* provider가 같은 sessionKey 의 thread 좌표를 내부에서 회송해 prompt cache 를 적중시킨다
|
|
@@ -27,7 +31,10 @@ type QgridProviderOptions = {
|
|
|
27
31
|
reasoningSummary?: "auto" | "concise" | "detailed" | "none";
|
|
28
32
|
/** OpenAI/codex service tier. OpenAI/codex route에서만 적용된다. */
|
|
29
33
|
serviceTier?: string;
|
|
30
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* @todo 향후 qgrid 서버 fallback routing에 사용할 후보 모델 목록.
|
|
36
|
+
* Claude Code가 처리하는 Fable 5 safety-refusal fallback과는 무관하다.
|
|
37
|
+
*/
|
|
31
38
|
fallbackModels?: string[];
|
|
32
39
|
/**
|
|
33
40
|
* codex 내장 image_generation tool 을 켠다. OpenAI/codex route + non-stream 전용.
|
|
@@ -44,13 +51,7 @@ type QgridProviderOptions = {
|
|
|
44
51
|
size?: "1024x1024" | "1024x1536" | "1536x1024";
|
|
45
52
|
};
|
|
46
53
|
};
|
|
47
|
-
|
|
48
|
-
* codex thread 좌표
|
|
49
|
-
* 멀티턴 대화에서 같은 대화를 같은 codex thread 로 라우팅 → conversation_id 고정 → 캐시 적중
|
|
50
|
-
* provider 내부 threadCoordStore 가 sessionKey 별로 관리
|
|
51
|
-
*/
|
|
52
|
-
|
|
53
|
-
type QgridSupportedModel = "openai/gpt-5.6-sol" | "openai/gpt-5.6-terra" | "openai/gpt-5.6-luna" | "openai/gpt-5.5" | "openai/gpt-5.4" | "openai/gpt-5.2" | "openai/gpt-5.4-mini" | "openai/gpt-5.3-codex" | "openai/gpt-5.3-codex-spark" | "anthropic/claude-haiku-4-5" | "anthropic/claude-sonnet-4" | "anthropic/claude-sonnet-4-5" | "anthropic/claude-sonnet-4-6" | "anthropic/claude-sonnet-4-7" | "anthropic/claude-sonnet-5" | "anthropic/claude-opus-4" | "anthropic/claude-opus-4-1" | "anthropic/claude-opus-4-5" | "anthropic/claude-opus-4-6" | "anthropic/claude-opus-4-7" | "anthropic/claude-opus-4-8";
|
|
54
|
+
type QgridSupportedModel = "openai/gpt-5.6-sol" | "openai/gpt-5.6-terra" | "openai/gpt-5.6-luna" | "openai/gpt-5.5" | "openai/gpt-5.4" | "openai/gpt-5.2" | "openai/gpt-5.4-mini" | "openai/gpt-5.3-codex" | "openai/gpt-5.3-codex-spark" | "anthropic/claude-fable-5" | "anthropic/claude-haiku-4-5" | "anthropic/claude-sonnet-4" | "anthropic/claude-sonnet-4-5" | "anthropic/claude-sonnet-4-6" | "anthropic/claude-sonnet-4-7" | "anthropic/claude-sonnet-5" | "anthropic/claude-opus-4" | "anthropic/claude-opus-4-1" | "anthropic/claude-opus-4-5" | "anthropic/claude-opus-4-6" | "anthropic/claude-opus-4-7" | "anthropic/claude-opus-4-8" | "anthropic/claude-opus-5";
|
|
54
55
|
type QgridLoggerConfig = {
|
|
55
56
|
/** qgrid 서버 주소. default: process.env.QGRID_URL, 없으면 기본값은 "http://localhost:44900" */
|
|
56
57
|
serverUrl?: string;
|
package/dist/index.js
CHANGED
|
@@ -356,6 +356,12 @@ function serializeHistory(messages) {
|
|
|
356
356
|
const DEFAULT_RUN_KEY = "__qgrid_default_run__";
|
|
357
357
|
const DEFAULT_STALE_RUN_TIMEOUT_MS = 1800 * 1e3;
|
|
358
358
|
const STALE_RUN_GRACE_MS = 5e3;
|
|
359
|
+
const OVERLAPPING_RUN_ERROR = "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.";
|
|
360
|
+
function fullModelName(provider, modelId) {
|
|
361
|
+
if (!provider || !modelId) return void 0;
|
|
362
|
+
const prefix = `${provider.split(".", 1)[0] ?? provider}/`;
|
|
363
|
+
return modelId.startsWith(prefix) ? modelId : `${prefix}${modelId}`;
|
|
364
|
+
}
|
|
359
365
|
function timedKeySet() {
|
|
360
366
|
const keys = /* @__PURE__ */ new Set();
|
|
361
367
|
const timers = /* @__PURE__ */ new Map();
|
|
@@ -385,8 +391,9 @@ function createQgridLogger(config = {}) {
|
|
|
385
391
|
const projectName = config.projectName ?? process.env.QGRID_PROJECT_NAME;
|
|
386
392
|
const onLogError = config.onLogError ?? ((e) => console.warn(`[qgrid-logger] ${e.message}`));
|
|
387
393
|
const runs = /* @__PURE__ */ new Map();
|
|
394
|
+
const starting = /* @__PURE__ */ new Map();
|
|
388
395
|
const keyTtl = typeof config.staleRunTimeoutMs === "number" && config.staleRunTimeoutMs > 0 ? config.staleRunTimeoutMs : DEFAULT_STALE_RUN_TIMEOUT_MS;
|
|
389
|
-
const
|
|
396
|
+
const suppressedRuns = timedKeySet();
|
|
390
397
|
const quarantined = timedKeySet();
|
|
391
398
|
const finalizeRun = async (runKey, result) => {
|
|
392
399
|
const run = runs.get(runKey);
|
|
@@ -403,6 +410,8 @@ function createQgridLogger(config = {}) {
|
|
|
403
410
|
toolCallId: pending.toolCallId,
|
|
404
411
|
toolName: pending.toolName,
|
|
405
412
|
toolArgs: pending.toolArgs,
|
|
413
|
+
modelName: pending.modelName,
|
|
414
|
+
requestedModelName: pending.requestedModelName,
|
|
406
415
|
toolDurationMs: run.toolDurations.get(pending.toolCallId)
|
|
407
416
|
}).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e)))));
|
|
408
417
|
run.pendingToolCalls = [];
|
|
@@ -412,6 +421,8 @@ function createQgridLogger(config = {}) {
|
|
|
412
421
|
status: result.status,
|
|
413
422
|
response: result.response,
|
|
414
423
|
tokenName: config.tokenName ?? "external",
|
|
424
|
+
modelName: result.modelName,
|
|
425
|
+
requestedModelName: result.requestedModelName ?? run.requestedModelName,
|
|
415
426
|
totalInputTokens: result.totalUsage?.inputTokens ?? 0,
|
|
416
427
|
totalOutputTokens: result.totalUsage?.outputTokens ?? 0,
|
|
417
428
|
totalCacheReadTokens: result.totalUsage?.inputTokenDetails?.cacheReadTokens ?? 0,
|
|
@@ -433,34 +444,63 @@ function createQgridLogger(config = {}) {
|
|
|
433
444
|
async onStart(event) {
|
|
434
445
|
if (!event.metadata?.qgridRunId && !event.functionId && event.metadata) event.metadata.qgridRunId = `auto-${++autoRunIdCounter}`;
|
|
435
446
|
const runKey = resolveRunKey(event);
|
|
447
|
+
if (event.model.provider === "qgrid") return;
|
|
436
448
|
if (quarantined.has(runKey)) {
|
|
437
449
|
onLogError(/* @__PURE__ */ new Error("createQgridLogger: telemetry key is quarantined after overlap"));
|
|
438
450
|
return;
|
|
439
451
|
}
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
452
|
+
if (runs.has(runKey) || starting.has(runKey) || suppressedRuns.has(runKey)) {
|
|
453
|
+
const reservation = starting.get(runKey);
|
|
454
|
+
if (reservation) {
|
|
455
|
+
reservation.errorMessage = OVERLAPPING_RUN_ERROR;
|
|
456
|
+
starting.delete(runKey);
|
|
457
|
+
}
|
|
458
|
+
suppressedRuns.remove(runKey);
|
|
459
|
+
quarantined.add(runKey, keyTtl);
|
|
460
|
+
const finalizing = finalizeRun(runKey, {
|
|
447
461
|
status: "error",
|
|
448
|
-
errorMessage:
|
|
462
|
+
errorMessage: OVERLAPPING_RUN_ERROR
|
|
449
463
|
});
|
|
450
|
-
|
|
451
|
-
|
|
464
|
+
onLogError(/* @__PURE__ */ new Error(OVERLAPPING_RUN_ERROR));
|
|
465
|
+
await finalizing;
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (event.providerOptions?.qgrid?.logger === false) {
|
|
469
|
+
suppressedRuns.add(runKey, keyTtl);
|
|
452
470
|
return;
|
|
453
471
|
}
|
|
472
|
+
const requestedModelName = fullModelName(event.model.provider, event.model.modelId) ?? event.model.modelId;
|
|
473
|
+
const reservation = {
|
|
474
|
+
requestedModelName,
|
|
475
|
+
startTime: Date.now()
|
|
476
|
+
};
|
|
477
|
+
starting.set(runKey, reservation);
|
|
454
478
|
try {
|
|
455
479
|
const messages = event.messages ?? (Array.isArray(event.prompt) ? event.prompt : void 0);
|
|
456
480
|
const history = serializeHistory(messages);
|
|
457
481
|
const result = await createRun(serverUrl, {
|
|
458
482
|
userPrompt: extractUserPrompt(event.prompt, messages),
|
|
459
483
|
systemPrompt: extractSystemPrompt(event.system),
|
|
460
|
-
modelName:
|
|
484
|
+
modelName: requestedModelName,
|
|
461
485
|
projectName,
|
|
462
486
|
history
|
|
463
487
|
});
|
|
488
|
+
if (reservation.errorMessage || quarantined.has(runKey) || starting.get(runKey) !== reservation) {
|
|
489
|
+
if (starting.get(runKey) === reservation) starting.delete(runKey);
|
|
490
|
+
await finishRun(serverUrl, {
|
|
491
|
+
requestLogId: result.requestLogId,
|
|
492
|
+
status: "error",
|
|
493
|
+
tokenName: config.tokenName ?? "external",
|
|
494
|
+
requestedModelName: reservation.requestedModelName,
|
|
495
|
+
totalInputTokens: 0,
|
|
496
|
+
totalOutputTokens: 0,
|
|
497
|
+
totalCacheReadTokens: 0,
|
|
498
|
+
totalCacheCreationTokens: 0,
|
|
499
|
+
totalDurationMs: Date.now() - reservation.startTime,
|
|
500
|
+
errorMessage: reservation.errorMessage ?? OVERLAPPING_RUN_ERROR
|
|
501
|
+
}).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e))));
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
464
504
|
let watchdogTimeout = DEFAULT_STALE_RUN_TIMEOUT_MS;
|
|
465
505
|
if (typeof config.staleRunTimeoutMs === "number") watchdogTimeout = config.staleRunTimeoutMs;
|
|
466
506
|
else if (typeof event.timeout === "number" && event.timeout > 0) watchdogTimeout = event.timeout + STALE_RUN_GRACE_MS;
|
|
@@ -493,8 +533,10 @@ function createQgridLogger(config = {}) {
|
|
|
493
533
|
cleanupAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
494
534
|
}
|
|
495
535
|
}
|
|
536
|
+
starting.delete(runKey);
|
|
496
537
|
runs.set(runKey, {
|
|
497
538
|
requestLogId: result.requestLogId,
|
|
539
|
+
requestedModelName,
|
|
498
540
|
pendingSteps: [],
|
|
499
541
|
pendingToolCalls: [],
|
|
500
542
|
startTime: Date.now(),
|
|
@@ -504,22 +546,27 @@ function createQgridLogger(config = {}) {
|
|
|
504
546
|
finishing: false
|
|
505
547
|
});
|
|
506
548
|
} catch (e) {
|
|
549
|
+
if (starting.get(runKey) === reservation) starting.delete(runKey);
|
|
507
550
|
onLogError(e instanceof Error ? e : new Error(String(e)));
|
|
508
551
|
}
|
|
509
552
|
},
|
|
510
553
|
onToolCallFinish(event) {
|
|
554
|
+
if (event.model?.provider === "qgrid") return;
|
|
511
555
|
const runKey = resolveRunKey(event);
|
|
512
|
-
if (
|
|
556
|
+
if (suppressedRuns.has(runKey) || quarantined.has(runKey)) return;
|
|
513
557
|
const run = runs.get(runKey);
|
|
514
558
|
if (!run) return;
|
|
515
559
|
run.toolDurations.set(event.toolCall.toolCallId, Math.round(event.durationMs));
|
|
516
560
|
},
|
|
517
561
|
onStepFinish(event) {
|
|
562
|
+
if (event.model?.provider === "qgrid") return;
|
|
518
563
|
const runKey = resolveRunKey(event);
|
|
519
|
-
if (
|
|
564
|
+
if (suppressedRuns.has(runKey) || quarantined.has(runKey)) return;
|
|
520
565
|
const run = runs.get(runKey);
|
|
521
566
|
if (!run) return;
|
|
522
567
|
const { content, usage, reasoningText, finishReason, stepNumber } = event;
|
|
568
|
+
const requestedModelName = fullModelName(event.model?.provider, event.model?.modelId) ?? run.requestedModelName;
|
|
569
|
+
const modelName = fullModelName(event.model?.provider, event.response?.modelId);
|
|
523
570
|
const remainingPending = [];
|
|
524
571
|
for (const pending of run.pendingToolCalls) {
|
|
525
572
|
const tr = content.find((p) => p.type === "tool-result" && p.toolCallId === pending.toolCallId);
|
|
@@ -533,6 +580,8 @@ function createQgridLogger(config = {}) {
|
|
|
533
580
|
toolCallId: pending.toolCallId,
|
|
534
581
|
toolName: pending.toolName,
|
|
535
582
|
toolArgs: pending.toolArgs,
|
|
583
|
+
modelName: pending.modelName,
|
|
584
|
+
requestedModelName: pending.requestedModelName,
|
|
536
585
|
toolResult: tr && "output" in tr ? safeStringify(tr.output) : void 0,
|
|
537
586
|
toolDurationMs: run.toolDurations.get(pending.toolCallId),
|
|
538
587
|
error: te && "error" in te ? safeStringify(te.error) : void 0
|
|
@@ -545,6 +594,8 @@ function createQgridLogger(config = {}) {
|
|
|
545
594
|
requestLogId: run.requestLogId,
|
|
546
595
|
stepIndex: stepNumber,
|
|
547
596
|
type: "generate",
|
|
597
|
+
modelName,
|
|
598
|
+
requestedModelName,
|
|
548
599
|
inputTokens: usage.inputTokens ?? 0,
|
|
549
600
|
outputTokens: usage.outputTokens ?? 0,
|
|
550
601
|
cacheReadTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
|
|
@@ -566,6 +617,8 @@ function createQgridLogger(config = {}) {
|
|
|
566
617
|
toolCallId: tc.toolCallId,
|
|
567
618
|
toolName: tc.toolName,
|
|
568
619
|
toolArgs: safeStringify(tc.input),
|
|
620
|
+
modelName,
|
|
621
|
+
requestedModelName,
|
|
569
622
|
toolResult: tr && "output" in tr ? safeStringify(tr.output) : void 0,
|
|
570
623
|
toolDurationMs: run.toolDurations.get(tc.toolCallId),
|
|
571
624
|
error: te && "error" in te ? safeStringify(te.error) : void 0
|
|
@@ -576,23 +629,31 @@ function createQgridLogger(config = {}) {
|
|
|
576
629
|
toolCallIndex: i,
|
|
577
630
|
toolCallId: tc.toolCallId,
|
|
578
631
|
toolName: tc.toolName,
|
|
579
|
-
toolArgs: safeStringify(tc.input)
|
|
632
|
+
toolArgs: safeStringify(tc.input),
|
|
633
|
+
modelName,
|
|
634
|
+
requestedModelName
|
|
580
635
|
});
|
|
581
636
|
}
|
|
582
637
|
},
|
|
583
638
|
async onFinish(event) {
|
|
639
|
+
if (event.model?.provider === "qgrid") return;
|
|
584
640
|
const runKey = resolveRunKey(event);
|
|
585
|
-
if (
|
|
586
|
-
|
|
641
|
+
if (suppressedRuns.has(runKey)) {
|
|
642
|
+
suppressedRuns.remove(runKey);
|
|
587
643
|
return;
|
|
588
644
|
}
|
|
589
645
|
if (quarantined.has(runKey)) return;
|
|
590
|
-
|
|
646
|
+
const run = runs.get(runKey);
|
|
647
|
+
if (!run) return;
|
|
591
648
|
const status = event.finishReason === "error" ? "error" : "succeeded";
|
|
649
|
+
const requestedModelName = fullModelName(event.model?.provider, event.model?.modelId) ?? run.requestedModelName;
|
|
650
|
+
const modelName = fullModelName(event.model?.provider, event.response?.modelId);
|
|
592
651
|
await finalizeRun(runKey, {
|
|
593
652
|
status,
|
|
594
653
|
response: event.text,
|
|
595
654
|
totalUsage: event.totalUsage,
|
|
655
|
+
modelName,
|
|
656
|
+
requestedModelName,
|
|
596
657
|
...status === "error" && "error" in event ? { errorMessage: getErrorMessage(event.error) } : {}
|
|
597
658
|
});
|
|
598
659
|
}
|
|
@@ -632,6 +693,19 @@ function setThreadCoord(sessionKey, coord) {
|
|
|
632
693
|
expiresAt: Date.now() + THREAD_COORD_TTL_MS
|
|
633
694
|
});
|
|
634
695
|
}
|
|
696
|
+
function qgridProviderMetadata(data) {
|
|
697
|
+
return { qgrid: {
|
|
698
|
+
model: data.model,
|
|
699
|
+
requestedModel: data.requestedModel ?? data.model,
|
|
700
|
+
modelFallbacks: data.modelFallbacks ?? [],
|
|
701
|
+
tokenName: data.tokenName ?? null,
|
|
702
|
+
durationMs: data.durationMs,
|
|
703
|
+
costUsd: data.costUsd,
|
|
704
|
+
costSource: data.costSource,
|
|
705
|
+
cacheCreation5mInputTokens: data.usage.cache_creation_5m_input_tokens ?? null,
|
|
706
|
+
cacheCreation1hInputTokens: data.usage.cache_creation_1h_input_tokens ?? null
|
|
707
|
+
} };
|
|
708
|
+
}
|
|
635
709
|
function reusableSessionKey(modelId, sessionKey) {
|
|
636
710
|
return modelId.startsWith("anthropic/") ? void 0 : sessionKey;
|
|
637
711
|
}
|
|
@@ -662,8 +736,8 @@ function qgrid(modelId, config) {
|
|
|
662
736
|
const clientRuns = /* @__PURE__ */ new Map();
|
|
663
737
|
let anonymousClientRunId = 0;
|
|
664
738
|
function clientRunKey(runContext) {
|
|
665
|
-
if (runContext
|
|
666
|
-
if (runContext
|
|
739
|
+
if (runContext?.requestLogId !== void 0) return `request:${runContext.requestLogId}`;
|
|
740
|
+
if (runContext?.threadCoord) {
|
|
667
741
|
const { workerId, epoch, threadId } = runContext.threadCoord;
|
|
668
742
|
return `thread:${workerId}:${epoch}:${threadId}`;
|
|
669
743
|
}
|
|
@@ -708,6 +782,7 @@ function qgrid(modelId, config) {
|
|
|
708
782
|
const verbosity = qgridOptions.verbosity;
|
|
709
783
|
const reasoningSummary = qgridOptions.reasoningSummary;
|
|
710
784
|
const serviceTier = qgridOptions.serviceTier;
|
|
785
|
+
const logger = qgridOptions.logger;
|
|
711
786
|
const imageGenerationOptions = qgridOptions.imageGenerationOptions;
|
|
712
787
|
if (imageGeneration) assertImageInputFitsJsonTransport(imageUrls);
|
|
713
788
|
const sessionKey = qgridOptions.sessionKey;
|
|
@@ -718,16 +793,13 @@ function qgrid(modelId, config) {
|
|
|
718
793
|
const jsonSchema = !hasTools && rawSchema && schemaType === "object" ? JSON.stringify(rawSchema) : void 0;
|
|
719
794
|
let runContext;
|
|
720
795
|
let toolResultsPayload;
|
|
721
|
-
let logMode;
|
|
722
796
|
const matchedClientRun = matchClientRun(options.prompt);
|
|
723
797
|
if (matchedClientRun) {
|
|
724
798
|
runContext = matchedClientRun.runContext;
|
|
725
799
|
toolResultsPayload = matchedClientRun.toolResultsPayload;
|
|
726
|
-
logMode = "run";
|
|
727
800
|
}
|
|
728
801
|
const storedCoord = reuseSessionKey ? getThreadCoord(reuseSessionKey) : void 0;
|
|
729
|
-
if (!
|
|
730
|
-
if (!logMode && hasTools) logMode = "run";
|
|
802
|
+
if (!matchedClientRun && storedCoord) runContext = { threadCoord: storedCoord };
|
|
731
803
|
const data = await fetch(`${serverUrl}/api/qgrid/query`, {
|
|
732
804
|
method: "POST",
|
|
733
805
|
headers: { "Content-Type": "application/json" },
|
|
@@ -744,7 +816,7 @@ function qgrid(modelId, config) {
|
|
|
744
816
|
...jsonSchema ? { jsonSchema } : {},
|
|
745
817
|
...history.length > 0 ? { history: JSON.stringify(history) } : {},
|
|
746
818
|
...projectName ? { projectName } : {},
|
|
747
|
-
...
|
|
819
|
+
...logger === false ? { logger: false } : {},
|
|
748
820
|
...runContext ? { runContext } : {},
|
|
749
821
|
...toolResultsPayload ? { toolResults: toolResultsPayload } : {},
|
|
750
822
|
...imageGeneration ? { imageGeneration } : {},
|
|
@@ -787,21 +859,16 @@ function qgrid(modelId, config) {
|
|
|
787
859
|
type: "text",
|
|
788
860
|
text: data.text
|
|
789
861
|
});
|
|
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.");
|
|
862
|
+
if (imageGeneration && finishReason.unified !== "tool-calls" && !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.");
|
|
791
863
|
if (matchedClientRun) clientRuns.delete(matchedClientRun.key);
|
|
792
|
-
if (
|
|
864
|
+
if (finishReason.unified === "tool-calls") rememberClientRun(data.runContext, content.filter((c) => c.type === "tool-call").map((c) => c.toolCallId));
|
|
793
865
|
if (reuseSessionKey && data.runContext?.threadCoord) setThreadCoord(reuseSessionKey, data.runContext.threadCoord);
|
|
794
866
|
return {
|
|
795
867
|
content,
|
|
796
868
|
finishReason,
|
|
797
869
|
usage: toAiSdkUsage(data.usage),
|
|
798
870
|
warnings: [],
|
|
799
|
-
providerMetadata:
|
|
800
|
-
model: data.model,
|
|
801
|
-
tokenName: data.tokenName ?? null,
|
|
802
|
-
durationMs: data.durationMs,
|
|
803
|
-
costUsd: data.costUsd
|
|
804
|
-
} },
|
|
871
|
+
providerMetadata: qgridProviderMetadata(data),
|
|
805
872
|
response: { modelId: data.model }
|
|
806
873
|
};
|
|
807
874
|
},
|
|
@@ -813,6 +880,7 @@ function qgrid(modelId, config) {
|
|
|
813
880
|
const verbosity = qgridOptions.verbosity;
|
|
814
881
|
const reasoningSummary = qgridOptions.reasoningSummary;
|
|
815
882
|
const serviceTier = qgridOptions.serviceTier;
|
|
883
|
+
const logger = qgridOptions.logger;
|
|
816
884
|
if (qgridOptions.imageGeneration) throw new Error("qgrid: imageGeneration is not supported with streamText — use generateText instead.");
|
|
817
885
|
const { prompt, system, history, droppedImageCount } = extractPromptAndHistory(options.prompt, { includeImages: false });
|
|
818
886
|
warnDroppedImages(droppedImageCount);
|
|
@@ -822,16 +890,13 @@ function qgrid(modelId, config) {
|
|
|
822
890
|
const jsonSchema = !hasTools && rawSchema && rawSchema.type === "object" ? JSON.stringify(rawSchema) : void 0;
|
|
823
891
|
let runContext;
|
|
824
892
|
let toolResultsPayload;
|
|
825
|
-
let logMode;
|
|
826
893
|
const matchedClientRun = matchClientRun(options.prompt);
|
|
827
894
|
if (matchedClientRun) {
|
|
828
895
|
runContext = matchedClientRun.runContext;
|
|
829
896
|
toolResultsPayload = matchedClientRun.toolResultsPayload;
|
|
830
|
-
logMode = "run";
|
|
831
897
|
}
|
|
832
898
|
const storedCoord = reuseSessionKey ? getThreadCoord(reuseSessionKey) : void 0;
|
|
833
|
-
if (!
|
|
834
|
-
if (!logMode && hasTools) logMode = "run";
|
|
899
|
+
if (!matchedClientRun && storedCoord) runContext = { threadCoord: storedCoord };
|
|
835
900
|
const prepRes = await fetch(`${serverUrl}/api/qgrid/prepareStream`, {
|
|
836
901
|
method: "POST",
|
|
837
902
|
headers: { "Content-Type": "application/json" },
|
|
@@ -847,7 +912,7 @@ function qgrid(modelId, config) {
|
|
|
847
912
|
...jsonSchema ? { jsonSchema } : {},
|
|
848
913
|
...history.length > 0 ? { history: JSON.stringify(history) } : {},
|
|
849
914
|
...projectName ? { projectName } : {},
|
|
850
|
-
...
|
|
915
|
+
...logger === false ? { logger: false } : {},
|
|
851
916
|
...runContext ? { runContext } : {},
|
|
852
917
|
...toolResultsPayload ? { toolResults: toolResultsPayload } : {}
|
|
853
918
|
} }),
|
|
@@ -895,7 +960,7 @@ function qgrid(modelId, config) {
|
|
|
895
960
|
}
|
|
896
961
|
const done = event.data;
|
|
897
962
|
if (matchedClientRun) clientRuns.delete(matchedClientRun.key);
|
|
898
|
-
if (done.
|
|
963
|
+
if (done.finishReason === "tool-calls") rememberClientRun(done.runContext, (done.content ?? []).filter((c) => c.type === "tool-call").map((c) => c.toolCallId));
|
|
899
964
|
if (reuseSessionKey && done.runContext?.threadCoord) setThreadCoord(reuseSessionKey, done.runContext.threadCoord);
|
|
900
965
|
if (done.content) {
|
|
901
966
|
for (const item of done.content) if (item.type === "text" && !deltaTextEmitted) {
|
|
@@ -936,6 +1001,10 @@ function qgrid(modelId, config) {
|
|
|
936
1001
|
});
|
|
937
1002
|
}
|
|
938
1003
|
}
|
|
1004
|
+
controller.enqueue({
|
|
1005
|
+
type: "response-metadata",
|
|
1006
|
+
modelId: done.model
|
|
1007
|
+
});
|
|
939
1008
|
controller.enqueue({
|
|
940
1009
|
type: "finish",
|
|
941
1010
|
finishReason: done.finishReason === "tool-calls" ? {
|
|
@@ -945,7 +1014,8 @@ function qgrid(modelId, config) {
|
|
|
945
1014
|
unified: "stop",
|
|
946
1015
|
raw: "stop"
|
|
947
1016
|
},
|
|
948
|
-
usage: toAiSdkUsage(done.usage)
|
|
1017
|
+
usage: toAiSdkUsage(done.usage),
|
|
1018
|
+
providerMetadata: qgridProviderMetadata(done)
|
|
949
1019
|
});
|
|
950
1020
|
streamCompleted = true;
|
|
951
1021
|
controller.close();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cartanova/qgrid-ai-sdk",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.9",
|
|
4
4
|
"description": "AI SDK LanguageModelV3 provider for qgrid",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@ai-sdk/provider": "^3.0.0",
|
|
24
24
|
"ai": "^6.0.190",
|
|
25
|
-
"tsdown": "^0.
|
|
25
|
+
"tsdown": "^0.22.3",
|
|
26
26
|
"typescript": "^5.9.3",
|
|
27
|
-
"vitest": "^4.
|
|
27
|
+
"vitest": "^4.1.10"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@ai-sdk/provider": "^3.0.0",
|
|
31
31
|
"ai": "^6.0.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"build": "tsdown
|
|
34
|
+
"build": "tsdown",
|
|
35
35
|
"test": "vitest run",
|
|
36
36
|
"e2e": "tsx e2e/e2e.ts",
|
|
37
37
|
"e2e:logger": "tsx e2e/e2e-logger.ts"
|