@cartanova/qgrid-ai-sdk 2.5.2 → 2.5.4

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 CHANGED
@@ -93,7 +93,7 @@ console.log(output.title, output.authors);
93
93
  top-level이 `object`인 schema는 서버 structured output으로 전달되어 강제됩니다.
94
94
  top-level이 `object`가 아니면 (예: array) AI SDK 클라이언트 파싱으로 fallback되며 경고 로그가 출력됩니다.
95
95
 
96
- > **Anthropic 모델 주의:** OpenAI/codex structured output은 디코딩 단계에서 schema를 강제(constrained decoding)하므로 이 부류 실패가 거의 없지만, Claude Code의 `--json-schema`는 StructuredOutput tool + 사후 검증 방식이라 복잡한 schema는 모델이 준수하지 못할 수 있습니다. qgrid는 Claude Code의 내부 재시도를 기본 차단하고(`MAX_STRUCTURED_OUTPUT_RETRIES=1` = 1회 시도, 1 미만 값은 1로 클램프) 검증 실패 시 깨진 JSON 대신 명시적 에러를 반환합니다. 서버 환경변수로 값을 올려 재시도를 허용할 수 있지만, 재시도 시 누적되는 rejected 출력이 스트리밍 경로의 AI SDK 파싱을 깨뜨릴 수 있어 기본값 유지를 권장합니다.
96
+ > **Anthropic 모델 주의:** OpenAI/codex structured output은 디코딩 단계에서 schema를 강제(constrained decoding)하므로 이 부류 실패가 거의 없지만, Claude Code의 `--json-schema`는 `StructuredOutput` tool + 사후 검증 방식이라 복잡한 schema는 모델이 준수하지 못할 수 있습니다. qgrid는 structured streaming에만 `MAX_STRUCTURED_OUTPUT_RETRIES=1`을 주입해 1회 시도로 제한합니다(1 미만 값은 1로 클램프). non-stream `generate`에는 이 override를 주입하지 않고 Claude Code의 기본 retry 예산을 사용합니다. 각 경로의 시도 이후 검증이 실패하면 깨진 JSON 대신 명시적 에러를 반환합니다.
97
97
 
98
98
  ### 스트리밍
99
99
 
@@ -114,7 +114,7 @@ for await (const chunk of textStream) {
114
114
  ### Tool Calling
115
115
 
116
116
  ```typescript
117
- import { generateText, tool } from "ai";
117
+ import { generateText, stepCountIs, tool } from "ai";
118
118
  import { qgrid } from "@cartanova/qgrid-ai-sdk";
119
119
  import { z } from "zod";
120
120
 
@@ -124,17 +124,54 @@ const { text } = await generateText({
124
124
  tools: {
125
125
  getWeather: tool({
126
126
  description: "도시의 현재 날씨 조회",
127
- parameters: z.object({ city: z.string() }),
127
+ inputSchema: z.object({ city: z.string() }),
128
128
  execute: async ({ city }) => {
129
129
  return { temperature: 22, condition: "맑음" };
130
130
  },
131
131
  }),
132
132
  },
133
+ stopWhen: stepCountIs(3),
133
134
  });
134
135
  ```
135
136
 
136
137
  tool-call은 qgrid 서버의 structured output emulation으로 동작합니다.
137
138
  AI SDK가 tool 실행을 관리하고, qgrid는 각 턴의 LLM 호출만 담당합니다.
139
+ 실행 가능한 tool을 쓸 때는 제한이 있는 `stopWhen`을 지정해야 AI SDK가
140
+ tool-call 턴 뒤에도 계속 진행해 모델의 최종 응답을 받습니다.
141
+
142
+ `tools`와 `Output.object`를 함께 사용할 수도 있습니다.
143
+
144
+ ```typescript
145
+ import { generateText, Output, stepCountIs, tool } from "ai";
146
+ import { qgrid } from "@cartanova/qgrid-ai-sdk";
147
+ import { z } from "zod";
148
+
149
+ const { output } = await generateText({
150
+ model: qgrid("openai/gpt-5.4"),
151
+ prompt: "서울 날씨를 조회해서 예보를 반환해줘.",
152
+ tools: {
153
+ getWeather: tool({
154
+ description: "도시의 현재 날씨 조회",
155
+ inputSchema: z.object({ city: z.string() }),
156
+ execute: async ({ city }) => ({ city, temperature: 22 }),
157
+ }),
158
+ },
159
+ stopWhen: stepCountIs(3),
160
+ output: Output.object({
161
+ schema: z.object({
162
+ city: z.string(),
163
+ summary: z.string(),
164
+ }),
165
+ }),
166
+ });
167
+ ```
168
+
169
+ qgrid 2.5.4는 모든 모델 턴에 합성된 action envelope를 강제합니다. tool-call
170
+ 턴은 AI SDK tool call로 유지되고, 마지막 `answer`는 사용자 schema로 강제된 뒤
171
+ `output`으로 반환됩니다. 제한이 있는 `stopWhen`을 지정하지 않으면 AI SDK가
172
+ 기본 첫 번째 step에서 멈춰 최종 structured output을 만들 수 없습니다. qgrid server
173
+ 2.5.4와 `@cartanova/qgrid-ai-sdk` 2.5.4가 모두 필요합니다. AI SDK의
174
+ `toolChoice`는 현재 전송하거나 강제하지 않으며 tool 선택은 모델이 결정합니다.
138
175
 
139
176
  ### Provider Options
140
177
 
@@ -168,10 +205,16 @@ const { text } = await generateText({
168
205
  | `verbosity` | `"low"` \| `"medium"` \| `"high"` | OpenAI 전용 | 응답 텍스트의 상세도 |
169
206
  | `reasoningSummary` | `"auto"` \| `"concise"` \| `"detailed"` \| `"none"` | OpenAI 전용 | 추론 요약 출력 방식 |
170
207
  | `serviceTier` | `string` | OpenAI 전용 | OpenAI/codex service tier |
208
+ | `timeoutMs` | 양의 정수, 최대 `1_800_000` | Anthropic 전용 | 서버의 Claude Code 프로세스 제한시간(ms). 기본값은 240초 |
171
209
  | `imageGeneration` | `boolean` | OpenAI 전용, non-stream | codex 내장 `image_generation` tool 활성화 ([아래](#image-generation) 참조) |
172
210
  | `imageGenerationOptions` | `{ quality?, size? }` | OpenAI 전용 | 이미지 품질/크기 힌트. `quality: "low" \| "medium" \| "high"`, `size: "1024x1024" \| "1024x1536" \| "1536x1024"` (기본: `medium` / `1536x1024`) |
173
211
  | `fallbackModels` | `string[]` | 예약 | 향후 qgrid 서버 fallback routing용 예약 필드. 현재 동작하지 않으며 Claude Code의 Fable refusal fallback과 무관 |
174
212
 
213
+ AI SDK 최상위 `timeout`은 전체 클라이언트 제한시간이며 custom provider 실행 전에
214
+ `AbortSignal`로 변환됩니다. 따라서 그 숫자 자체는 qgrid에 전달되지 않습니다. qgrid 서버의
215
+ Claude Code 프로세스 제한시간을 바꾸려면 `providerOptions.qgrid.timeoutMs`를 사용하세요.
216
+ 클라이언트 취소 또는 비스트리밍 HTTP 연결 종료도 서버의 provider 실행을 중단합니다.
217
+
175
218
  ### 멀티턴 prompt cache (sessionKey)
176
219
 
177
220
  멀티턴 대화에서 `sessionKey`로 호출자의 도메인 ID(게임 세션 ID, 채팅방 ID 등) 하나만 넘기면, provider가 같은 대화를 같은 codex thread로 라우팅하여 prompt cache를 적중시킵니다. thread 좌표의 발급/보관/회송은 provider 내부에서 처리하므로 호출자는 key만 넘기면 됩니다.
@@ -363,7 +406,30 @@ qgrid(modelId, {
363
406
 
364
407
  - `temperature`, `maxOutputTokens` 등 sampling 파라미터는 CLI 런타임(OpenAI: codex app-server, Anthropic: Claude Code)이 지원하지 않아 무시됩니다.
365
408
  - Structured output은 top-level `object` schema만 서버에서 강제됩니다. top-level `array`는 클라이언트 파싱 fallback.
366
- - `tools`와 structured output schema는 서버에서 동시에 사용할 수 없습니다. tools가 있으면 schema는 서버로 전달되지 않고 최종 텍스트를 AI SDK가 클라이언트에서 파싱합니다.
409
+ - `tools`와 `Output.object`를 함께 사용하려면 qgrid server와 AI SDK가 모두 2.5.4 이상이어야 합니다.
410
+ - AI SDK/Zod가 Draft-7 `items: [...]`로 만드는 위치 기반 tuple은 지원되는
411
+ positive schema 위치에서 OpenAI 호출 전에 정규화되고 위치별 제약이
412
+ 강제됩니다. tuple tail이 생략되면 고정 길이로 해석하며,
413
+ `additionalItems: true`로 명시한 무제한 tail은 HTTP 400으로 거부합니다.
414
+ negative, conditional 등 안전하게 정규화할 수 없는 위치의 tuple도 의미가
415
+ 바뀌는 변환 대신 HTTP 400으로 거부합니다. definition은 전역 정규화되므로
416
+ 해당 위치에서 참조하는 경우도 같은 이유로 거부합니다. Anthropic 위치 기반
417
+ tuple schema는 Claude Code가 위치 의미를 보존할 수 없어 HTTP 400으로
418
+ 거부됩니다. tuple node는 `type: "array"`를 명시해야 하며 nullable tuple은
419
+ array/null `anyOf`로 표현합니다.
420
+ - structured schema에서는 문서 root 또는 `$defs`/`definitions` entry root만
421
+ 연속으로 가리키는 로컬 root-relative JSON Pointer `$ref`를 허용합니다.
422
+ property, tuple 내부, conditional, literal 값을 가리키는 ref는 정규화 중
423
+ target이 이동하거나 다시 작성될 수 있어 HTTP 400으로 거부합니다. resource
424
+ ID, anchor, 외부 ref, dynamic ref, recursive ref도 허용하지 않습니다.
425
+ - output/tool schema serialization, tool 이름, 설명, JSON escaping,
426
+ composition framing은 합산 UTF-8 512 KiB 전처리 한도를 공유합니다.
427
+ schema 값에는 별도로 합산 20,000 node와 schema별 최대 깊이 128 한도가
428
+ 적용됩니다. 잘못되거나 한도를 넘는 입력은 provider 실행 전에 HTTP 400으로
429
+ 실패합니다.
430
+ - Anthropic 경로에서는 최종 합성 schema가 Claude Code 전송의 안전한 단일 argv
431
+ 한도인 64 KiB도 넘지 않아야 합니다.
432
+ - AI SDK의 `toolChoice`는 현재 qgrid에서 지원하지 않습니다.
367
433
 
368
434
  ## 요구사항
369
435
 
package/README.md CHANGED
@@ -93,7 +93,7 @@ console.log(output.title, output.authors);
93
93
  Schemas with a top-level `object` are forwarded to server-side structured output and enforced.
94
94
  If the top level is not an `object` (e.g. array), the AI SDK falls back to client-side parsing and a warning is logged.
95
95
 
96
- > **Note for Anthropic models:** OpenAI/codex structured output constrains decoding to the schema, so this class of failure is rare there. Claude Code's `--json-schema`, however, works as a StructuredOutput tool + post-hoc validation, so the model may fail to honor a complex schema. qgrid disables Claude Code's internal retries by default (`MAX_STRUCTURED_OUTPUT_RETRIES=1` = one attempt; values below 1 are clamped to 1) and returns an explicit error on validation failure instead of broken JSON. You can raise the server env var to allow retries, but rejected attempts accumulating on the streaming path can break AI SDK parsing, so keeping the default is recommended.
96
+ > **Note for Anthropic models:** OpenAI/codex structured output constrains decoding to the schema, so this class of failure is rare there. Claude Code's `--json-schema`, however, works as a `StructuredOutput` tool plus post-hoc validation, so the model may fail to honor a complex schema. For structured streaming only, qgrid sets `MAX_STRUCTURED_OUTPUT_RETRIES=1` (one attempt; values below 1 are clamped to 1) to bound stream latency. Non-streaming `generate` leaves that override unset and uses Claude Code's default retry budget. Validation failures after the applicable attempts return an explicit error instead of broken JSON.
97
97
 
98
98
  ### Streaming
99
99
 
@@ -114,7 +114,7 @@ for await (const chunk of textStream) {
114
114
  ### Tool Calling
115
115
 
116
116
  ```typescript
117
- import { generateText, tool } from "ai";
117
+ import { generateText, stepCountIs, tool } from "ai";
118
118
  import { qgrid } from "@cartanova/qgrid-ai-sdk";
119
119
  import { z } from "zod";
120
120
 
@@ -124,17 +124,55 @@ const { text } = await generateText({
124
124
  tools: {
125
125
  getWeather: tool({
126
126
  description: "Get the current weather for a city",
127
- parameters: z.object({ city: z.string() }),
127
+ inputSchema: z.object({ city: z.string() }),
128
128
  execute: async ({ city }) => {
129
129
  return { temperature: 22, condition: "sunny" };
130
130
  },
131
131
  }),
132
132
  },
133
+ stopWhen: stepCountIs(3),
133
134
  });
134
135
  ```
135
136
 
136
137
  Tool calls work through the qgrid server's structured output emulation.
137
138
  The AI SDK manages tool execution; qgrid handles only each turn's LLM call.
139
+ Executable tools require a bounded `stopWhen` so the AI SDK continues after a
140
+ tool-call step and asks the model for the final response.
141
+
142
+ Tools can be combined with `Output.object`:
143
+
144
+ ```typescript
145
+ import { generateText, Output, stepCountIs, tool } from "ai";
146
+ import { qgrid } from "@cartanova/qgrid-ai-sdk";
147
+ import { z } from "zod";
148
+
149
+ const { output } = await generateText({
150
+ model: qgrid("openai/gpt-5.4"),
151
+ prompt: "Look up Seoul's weather and return a forecast.",
152
+ tools: {
153
+ getWeather: tool({
154
+ description: "Get the current weather for a city",
155
+ inputSchema: z.object({ city: z.string() }),
156
+ execute: async ({ city }) => ({ city, temperature: 22 }),
157
+ }),
158
+ },
159
+ stopWhen: stepCountIs(3),
160
+ output: Output.object({
161
+ schema: z.object({
162
+ city: z.string(),
163
+ summary: z.string(),
164
+ }),
165
+ }),
166
+ });
167
+ ```
168
+
169
+ qgrid 2.5.4 constrains every model turn with a composed action envelope. Tool-call
170
+ turns remain AI SDK tool calls, while the final `answer` is constrained by the
171
+ user schema and returned as `output`. Keep a bounded `stopWhen`; otherwise AI SDK
172
+ stops after its default first step and cannot produce the final structured output.
173
+ This requires both qgrid server 2.5.4 and `@cartanova/qgrid-ai-sdk` 2.5.4. AI SDK
174
+ `toolChoice` is not currently transported or enforced; tool selection remains
175
+ model-driven.
138
176
 
139
177
  ### Provider Options
140
178
 
@@ -168,10 +206,17 @@ const { text } = await generateText({
168
206
  | `verbosity` | `"low"` \| `"medium"` \| `"high"` | OpenAI only | Response text verbosity |
169
207
  | `reasoningSummary` | `"auto"` \| `"concise"` \| `"detailed"` \| `"none"` | OpenAI only | Reasoning summary output mode |
170
208
  | `serviceTier` | `string` | OpenAI only | OpenAI/codex service tier |
209
+ | `timeoutMs` | positive integer, max `1_800_000` | Anthropic only | Server-side Claude Code process timeout in milliseconds. Defaults to 240 seconds |
171
210
  | `imageGeneration` | `boolean` | OpenAI only, non-stream | Enables codex's built-in `image_generation` tool (see [below](#image-generation)) |
172
211
  | `imageGenerationOptions` | `{ quality?, size? }` | OpenAI only | Image quality/size hints. `quality: "low" \| "medium" \| "high"`, `size: "1024x1024" \| "1024x1536" \| "1536x1024"` (defaults: `medium` / `1536x1024`) |
173
212
  | `fallbackModels` | `string[]` | reserved | Reserved for future qgrid server-side fallback routing. Not functional yet and unrelated to Claude Code's Fable refusal fallback |
174
213
 
214
+ AI SDK's top-level `timeout` remains the overall client-side budget and is converted to an
215
+ `AbortSignal` before the custom provider runs. It does not expose the numeric timeout to qgrid.
216
+ Use `providerOptions.qgrid.timeoutMs` when the qgrid server's Claude Code process needs a different
217
+ limit. A client abort or disconnected non-streaming HTTP request also terminates the server-side
218
+ provider execution.
219
+
175
220
  ### Multi-turn prompt cache (sessionKey)
176
221
 
177
222
  For multi-turn conversations, pass a single caller-side domain ID (game session ID, chat room ID, ...) as `sessionKey` and the provider routes the same conversation to the same codex thread, hitting the prompt cache. Thread coordinate issuance/storage/replay is handled inside the provider — the caller only passes the key.
@@ -363,7 +408,33 @@ If multiple projects/workflows share one qgrid server, set `QGRID_PROJECT_NAME`.
363
408
 
364
409
  - Sampling parameters such as `temperature` and `maxOutputTokens` are ignored — the CLI runtimes (OpenAI: codex app-server, Anthropic: Claude Code) do not support them.
365
410
  - Structured output is server-enforced only for top-level `object` schemas. Top-level `array` falls back to client-side parsing.
366
- - `tools` and a structured output schema cannot be used together on the server. When tools are present, the schema is not forwarded and the AI SDK parses the final text client-side.
411
+ - Combining `tools` with `Output.object` requires qgrid server and AI SDK 2.5.4 or later.
412
+ - AI SDK/Zod positional tuples emitted as Draft-7 `items: [...]` are normalized
413
+ in supported positive schema positions before OpenAI dispatch and their
414
+ positional constraints are enforced. An omitted tuple tail is treated as
415
+ fixed-length; an explicitly unrestricted `additionalItems: true` tail is
416
+ rejected with HTTP 400. Tuples in negative, conditional, or otherwise
417
+ non-normalizable schema positions are also rejected instead of being
418
+ rewritten with changed semantics. References from those positions are
419
+ rejected for the same reason because definitions are normalized globally.
420
+ Anthropic positional tuple schemas are rejected with HTTP 400 because Claude
421
+ Code cannot preserve their positional semantics. Tuple nodes must explicitly
422
+ declare `type: "array"`; express nullable tuples with an `anyOf` array/null
423
+ branch.
424
+ - Structured schemas accept only local root-relative JSON Pointer `$ref` values
425
+ targeting the document root or a chain of `$defs`/`definitions` entry roots.
426
+ References into properties, tuple internals, conditionals, or literal values
427
+ fail with HTTP 400 because normalization can move or rewrite those targets.
428
+ Resource IDs, anchors, external refs, dynamic refs, and recursive refs are
429
+ also rejected.
430
+ - Output/tool schema serialization, tool names, descriptions, JSON escaping,
431
+ and composition framing share an aggregate 512 KiB UTF-8 preprocessing
432
+ budget. Schema values also share a 20,000-node budget and have a maximum
433
+ depth of 128 per schema. Invalid or over-budget inputs fail with HTTP 400
434
+ before provider execution.
435
+ - On Anthropic routes, the final composed schema must also fit the 64 KiB safe
436
+ single-argument budget used by the Claude Code transport.
437
+ - AI SDK `toolChoice` is not currently supported by qgrid.
367
438
 
368
439
  ## Requirements
369
440
 
package/dist/index.d.ts CHANGED
@@ -31,6 +31,12 @@ type QgridProviderOptions = {
31
31
  reasoningSummary?: "auto" | "concise" | "detailed" | "none";
32
32
  /** OpenAI/codex service tier. OpenAI/codex route에서만 적용된다. */
33
33
  serviceTier?: string;
34
+ /**
35
+ * qgrid 서버의 provider 실행 제한(ms). Anthropic 경로에서는 Claude Code 프로세스 타이머로
36
+ * 사용된다. AI SDK 표준 timeout은 provider에 숫자를 전달하지 않고 abortSignal로 변환되므로,
37
+ * 서버 측 제한을 바꾸려면 이 옵션을 사용한다. 양의 정수만 허용하며 최대 30분, 기본 240초다.
38
+ */
39
+ timeoutMs?: number;
34
40
  /**
35
41
  * @todo 향후 qgrid 서버 fallback routing에 사용할 후보 모델 목록.
36
42
  * Claude Code가 처리하는 Fable 5 safety-refusal fallback과는 무관하다.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  const QGRID_PROVIDER_NAME = "qgrid";
3
3
  const THREAD_COORD_TTL_MS = 10 * 6e4;
4
4
  const TEXT_STREAM_ID_PREFIX = "text";
5
- const TOP_LEVEL_SCHEMA_WARNING = "OpenAI structured output requires \"object\". Falling back to client-side parsing.";
5
+ const TOP_LEVEL_SCHEMA_WARNING = "qgrid server-enforced structured output requires \"object\". Falling back to client-side parsing.";
6
6
  const PENDING_TOOL_RESULTS_WARNING = "[qgrid] tool results did not match any pending qgrid run; sending request without runContext";
7
7
  //#endregion
8
8
  //#region src/utils.ts
@@ -678,6 +678,16 @@ function warnDroppedImages(droppedImageCount) {
678
678
  if (droppedImageCount <= 0) return;
679
679
  console.warn(`[qgrid] ${droppedImageCount} image message part(s) were ignored because providerOptions.qgrid.imageGeneration is not enabled.`);
680
680
  }
681
+ function serializeObjectResponseFormat(responseFormat) {
682
+ const rawSchema = responseFormat?.type === "json" ? responseFormat.schema : void 0;
683
+ if (rawSchema === void 0) return void 0;
684
+ const schemaType = rawSchema.type;
685
+ if (schemaType !== "object") {
686
+ console.warn(`[qgrid] responseFormat.schema top-level type is "${schemaType ?? "unknown"}". ${TOP_LEVEL_SCHEMA_WARNING}`);
687
+ return;
688
+ }
689
+ return JSON.stringify(rawSchema);
690
+ }
681
691
  function getThreadCoord(sessionKey) {
682
692
  const entry = threadCoordStore.get(sessionKey);
683
693
  if (!entry) return void 0;
@@ -782,15 +792,13 @@ function qgrid(modelId, config) {
782
792
  const verbosity = qgridOptions.verbosity;
783
793
  const reasoningSummary = qgridOptions.reasoningSummary;
784
794
  const serviceTier = qgridOptions.serviceTier;
795
+ const timeoutMs = qgridOptions.timeoutMs;
785
796
  const logger = qgridOptions.logger;
786
797
  const imageGenerationOptions = qgridOptions.imageGenerationOptions;
787
798
  if (imageGeneration) assertImageInputFitsJsonTransport(imageUrls);
788
799
  const sessionKey = qgridOptions.sessionKey;
789
800
  const reuseSessionKey = reusableSessionKey(modelId, sessionKey);
790
- const rawSchema = options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0;
791
- const schemaType = rawSchema ? rawSchema.type : void 0;
792
- if (rawSchema && !hasTools && schemaType !== "object") console.warn(`[qgrid] responseFormat.schema top-level type is "${schemaType ?? "unknown"}". ${TOP_LEVEL_SCHEMA_WARNING}`);
793
- const jsonSchema = !hasTools && rawSchema && schemaType === "object" ? JSON.stringify(rawSchema) : void 0;
801
+ const jsonSchema = serializeObjectResponseFormat(options.responseFormat);
794
802
  let runContext;
795
803
  let toolResultsPayload;
796
804
  const matchedClientRun = matchClientRun(options.prompt);
@@ -812,6 +820,7 @@ function qgrid(modelId, config) {
812
820
  ...verbosity ? { verbosity } : {},
813
821
  ...reasoningSummary ? { reasoningSummary } : {},
814
822
  ...serviceTier ? { serviceTier } : {},
823
+ ...timeoutMs !== void 0 ? { timeout: timeoutMs } : {},
815
824
  ...hasTools ? { tools: tools.map(toQgridTool) } : {},
816
825
  ...jsonSchema ? { jsonSchema } : {},
817
826
  ...history.length > 0 ? { history: JSON.stringify(history) } : {},
@@ -880,14 +889,14 @@ function qgrid(modelId, config) {
880
889
  const verbosity = qgridOptions.verbosity;
881
890
  const reasoningSummary = qgridOptions.reasoningSummary;
882
891
  const serviceTier = qgridOptions.serviceTier;
892
+ const timeoutMs = qgridOptions.timeoutMs;
883
893
  const logger = qgridOptions.logger;
884
894
  if (qgridOptions.imageGeneration) throw new Error("qgrid: imageGeneration is not supported with streamText — use generateText instead.");
885
895
  const { prompt, system, history, droppedImageCount } = extractPromptAndHistory(options.prompt, { includeImages: false });
886
896
  warnDroppedImages(droppedImageCount);
887
897
  const sessionKey = qgridOptions.sessionKey;
888
898
  const reuseSessionKey = reusableSessionKey(modelId, sessionKey);
889
- const rawSchema = options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0;
890
- const jsonSchema = !hasTools && rawSchema && rawSchema.type === "object" ? JSON.stringify(rawSchema) : void 0;
899
+ const jsonSchema = serializeObjectResponseFormat(options.responseFormat);
891
900
  let runContext;
892
901
  let toolResultsPayload;
893
902
  const matchedClientRun = matchClientRun(options.prompt);
@@ -908,6 +917,7 @@ function qgrid(modelId, config) {
908
917
  ...verbosity ? { verbosity } : {},
909
918
  ...reasoningSummary ? { reasoningSummary } : {},
910
919
  ...serviceTier ? { serviceTier } : {},
920
+ ...timeoutMs !== void 0 ? { timeout: timeoutMs } : {},
911
921
  ...hasTools ? { tools: tools.map(toQgridTool) } : {},
912
922
  ...jsonSchema ? { jsonSchema } : {},
913
923
  ...history.length > 0 ? { history: JSON.stringify(history) } : {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-ai-sdk",
3
- "version": "2.5.2",
3
+ "version": "2.5.4",
4
4
  "description": "AI SDK LanguageModelV3 provider for qgrid",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,7 +24,8 @@
24
24
  "ai": "^6.0.190",
25
25
  "tsdown": "^0.22.3",
26
26
  "typescript": "^5.9.3",
27
- "vitest": "^4.1.10"
27
+ "vitest": "^4.1.10",
28
+ "zod": "4.4.3"
28
29
  },
29
30
  "peerDependencies": {
30
31
  "@ai-sdk/provider": "^3.0.0",
@@ -34,6 +35,7 @@
34
35
  "build": "tsdown",
35
36
  "test": "vitest run",
36
37
  "e2e": "tsx e2e/e2e.ts",
38
+ "e2e:tools-output": "tsx e2e/tools-structured-output.ts",
37
39
  "e2e:logger": "tsx e2e/e2e-logger.ts"
38
40
  }
39
41
  }