@cartanova/qgrid-ai-sdk 2.3.8 → 2.3.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.
Files changed (3) hide show
  1. package/README.ko.md +303 -0
  2. package/README.md +115 -63
  3. package/package.json +3 -2
package/README.ko.md ADDED
@@ -0,0 +1,303 @@
1
+ # @cartanova/qgrid-ai-sdk
2
+
3
+ [English](./README.md) · **한국어**
4
+
5
+ AI SDK v6 custom `LanguageModelV3` provider for [qgrid](https://github.com/cartanova-ai/Qgrid).
6
+
7
+ **기존 AI SDK 코드 변경 없이, `model` 한 줄만 바꾸면 구독 토큰 풀링(토큰 N개 × worker 병렬 처리) + request log 대시보드를 사용할 수 있습니다.**
8
+
9
+ ```diff
10
+ import { generateText } from "ai";
11
+ -import { openai } from "@ai-sdk/openai";
12
+ +import { qgrid } from "@cartanova/qgrid-ai-sdk";
13
+
14
+ const { text } = await generateText({
15
+ - model: openai("gpt-5.4-mini"),
16
+ + model: qgrid("openai/gpt-5.4-mini"),
17
+ prompt: "서울 날씨 알려줘",
18
+ });
19
+ ```
20
+
21
+ 이미 다른 provider(google, openai 등)를 직접 사용하고 있다면, **logger 옵션 한 줄**만 추가하면 에이전트의 매 step(generate, tool-call, reasoning)을 qgrid 대시보드에서 확인할 수 있습니다.
22
+
23
+ ```diff
24
+ const { text } = await generateText({
25
+ model: google("gemini-3-flash"),
26
+ prompt: "복잡한 질문",
27
+ + experimental_telemetry: createQgridLogger({ serverUrl: "http://localhost:44900" }),
28
+ });
29
+ ```
30
+
31
+ ## 설치
32
+
33
+ ```bash
34
+ pnpm add @cartanova/qgrid-ai-sdk
35
+ ```
36
+
37
+ Peer dependencies: `ai@^6.0.0`, `@ai-sdk/provider@^3.0.0`
38
+
39
+ ## 빠른 시작
40
+
41
+ ```typescript
42
+ import { generateText } from "ai";
43
+ import { qgrid } from "@cartanova/qgrid-ai-sdk";
44
+
45
+ const { text } = await generateText({
46
+ model: qgrid("openai/gpt-5.4-mini"),
47
+ prompt: "서울 날씨 알려줘",
48
+ });
49
+ ```
50
+
51
+ qgrid 서버(`http://localhost:44900`)가 실행 중이어야 합니다.
52
+
53
+ ## 사용법
54
+ > 들어가기전에: 모든 클라이언트 사용법은 [AI-SDK](https://ai-sdk.dev/docs/ai-sdk-core)와 동일합니다.
55
+
56
+ ### 텍스트 생성
57
+
58
+ ```typescript
59
+ import { generateText } from "ai";
60
+ import { qgrid } from "@cartanova/qgrid-ai-sdk";
61
+
62
+ const { text } = await generateText({
63
+ model: qgrid("openai/gpt-5.4-mini"),
64
+ system: "당신은 학술 논문 요약가입니다.",
65
+ prompt: paperText,
66
+ });
67
+ ```
68
+
69
+ ### 구조화 응답 (Structured Output)
70
+ > [AI-SDK structured output guide를 참조하세요](https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data)
71
+
72
+ ```typescript
73
+ import { generateText, Output } from "ai";
74
+ import { qgrid } from "@cartanova/qgrid-ai-sdk";
75
+ import { z } from "zod";
76
+
77
+ const { output } = await generateText({
78
+ model: qgrid("openai/gpt-5.4"),
79
+ system: "논문 메타데이터를 추출해주세요.",
80
+ prompt: paperText,
81
+ output: Output.object({
82
+ schema: z.object({
83
+ title: z.string(),
84
+ authors: z.array(z.string()),
85
+ keyFindings: z.array(z.string()),
86
+ }),
87
+ }),
88
+ });
89
+
90
+ console.log(output.title, output.authors);
91
+ ```
92
+
93
+ top-level이 `object`인 schema는 서버 structured output으로 전달되어 강제됩니다.
94
+ top-level이 `object`가 아니면 (예: array) AI SDK 클라이언트 파싱으로 fallback되며 경고 로그가 출력됩니다.
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 파싱을 깨뜨릴 수 있어 기본값 유지를 권장합니다.
97
+
98
+ ### 스트리밍
99
+
100
+ ```typescript
101
+ import { streamText } from "ai";
102
+ import { qgrid } from "@cartanova/qgrid-ai-sdk";
103
+
104
+ const { textStream } = streamText({
105
+ model: qgrid("openai/gpt-5.4-mini"),
106
+ prompt: "TypeScript의 장점을 설명해줘",
107
+ });
108
+
109
+ for await (const chunk of textStream) {
110
+ process.stdout.write(chunk);
111
+ }
112
+ ```
113
+
114
+ ### Tool Calling
115
+
116
+ ```typescript
117
+ import { generateText, tool } from "ai";
118
+ import { qgrid } from "@cartanova/qgrid-ai-sdk";
119
+ import { z } from "zod";
120
+
121
+ const { text } = await generateText({
122
+ model: qgrid("openai/gpt-5.4-mini"),
123
+ prompt: "서울 날씨 알려줘",
124
+ tools: {
125
+ getWeather: tool({
126
+ description: "도시의 현재 날씨 조회",
127
+ parameters: z.object({ city: z.string() }),
128
+ execute: async ({ city }) => {
129
+ return { temperature: 22, condition: "맑음" };
130
+ },
131
+ }),
132
+ },
133
+ });
134
+ ```
135
+
136
+ tool-call은 qgrid 서버의 structured output emulation으로 동작합니다.
137
+ AI SDK가 tool 실행을 관리하고, qgrid는 각 턴의 LLM 호출만 담당합니다.
138
+
139
+ ### Provider Options
140
+
141
+ qgrid 전용 옵션은 전부 `providerOptions.qgrid` 네임스페이스로 전달합니다. (`providerOptions.openai`가 아닙니다)
142
+
143
+ ```typescript
144
+ import { generateText } from "ai";
145
+ import { qgrid, type QgridProviderOptions } from "@cartanova/qgrid-ai-sdk";
146
+
147
+ const { text } = await generateText({
148
+ model: qgrid("openai/gpt-5.4"),
149
+ prompt: "복잡한 문제를 분석해줘",
150
+ providerOptions: {
151
+ qgrid: {
152
+ effort: "high",
153
+ reasoningSummary: "concise",
154
+ verbosity: "medium",
155
+ } satisfies QgridProviderOptions,
156
+ },
157
+ });
158
+ ```
159
+
160
+ | 옵션 | 값 | 적용 범위 | 설명 |
161
+ |---|---|---|---|
162
+ | `sessionKey` | `string` | OpenAI 전용 | 멀티턴 대화 식별자. 같은 key는 같은 codex thread로 라우팅되어 prompt cache 적중 ([아래](#멀티턴-prompt-cache-sessionkey) 참조) |
163
+ | `effort` | `"none"` \| `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` | 공통 | reasoning 모델의 추론 깊이. 기본값은 config의 `defaultEffort` (`"low"`) |
164
+ | `verbosity` | `"low"` \| `"medium"` \| `"high"` | OpenAI 전용 | 응답 텍스트의 상세도 |
165
+ | `reasoningSummary` | `"auto"` \| `"concise"` \| `"detailed"` \| `"none"` | OpenAI 전용 | 추론 요약 출력 방식 |
166
+ | `serviceTier` | `string` | OpenAI 전용 | OpenAI/codex service tier |
167
+ | `imageGeneration` | `boolean` | OpenAI 전용, non-stream | codex 내장 `image_generation` tool 활성화 ([아래](#image-generation) 참조) |
168
+ | `imageGenerationOptions` | `{ quality?, size? }` | OpenAI 전용 | 이미지 품질/크기 힌트. `quality: "low" \| "medium" \| "high"`, `size: "1024x1024" \| "1024x1536" \| "1536x1024"` (기본: `medium` / `1536x1024`) |
169
+ | `fallbackModels` | `string[]` | 예약 | 향후 서버 fallback routing용 예약 필드. 현재 동작하지 않음 |
170
+
171
+ ### 멀티턴 prompt cache (sessionKey)
172
+
173
+ 멀티턴 대화에서 `sessionKey`로 호출자의 도메인 ID(게임 세션 ID, 채팅방 ID 등) 하나만 넘기면, provider가 같은 대화를 같은 codex thread로 라우팅하여 prompt cache를 적중시킵니다. thread 좌표의 발급/보관/회송은 provider 내부에서 처리하므로 호출자는 key만 넘기면 됩니다.
174
+
175
+ ```typescript
176
+ const { text } = await generateText({
177
+ model: qgrid("openai/gpt-5.4-mini"),
178
+ prompt: nextTurnPrompt,
179
+ providerOptions: { qgrid: { sessionKey: "game-session-123" } },
180
+ });
181
+ ```
182
+
183
+ - thread는 idle 10분 후 만료됩니다. 만료되거나 worker가 재시작되면 자동으로 cold thread로 시작하므로 요청이 실패하지는 않습니다.
184
+ - `anthropic/*` 모델에서는 무시됩니다 (Claude Code는 요청마다 fresh process를 띄우므로 thread reuse가 없음).
185
+ - 서버에서 `QGRID_OPENAI_THREAD_REUSE=false`로 비활성화할 수 있습니다.
186
+
187
+ ### Image Generation
188
+
189
+ OpenAI/codex 경로 전용, `generateText` 전용입니다. 해당 요청에만 codex 내장 `image_generation` tool을 켜고, 결과 이미지를 AI SDK `files`로 받습니다.
190
+
191
+ ```typescript
192
+ const result = await generateText({
193
+ model: qgrid("openai/gpt-5.4"),
194
+ prompt: "우주를 나는 고래 일러스트",
195
+ providerOptions: {
196
+ qgrid: {
197
+ imageGeneration: true,
198
+ imageGenerationOptions: { quality: "medium", size: "1536x1024" },
199
+ },
200
+ },
201
+ });
202
+
203
+ const image = result.files[0]; // mediaType: "image/png", base64
204
+ ```
205
+
206
+ - `streamText`에서는 거부됩니다 (non-stream 전용).
207
+ - 이미지 생성 요청은 항상 cold one-shot thread로 실행되며 thread reuse 대상에서 제외됩니다.
208
+ - 이미지 비용은 `gpt-image-2` 공개 단가표 기반 **추정치**로 request log의 `image_cost_usd`에 별도 기록됩니다 (codex가 정확한 이미지 tool 사용량을 노출하지 않음).
209
+
210
+ ## Telemetry Logger
211
+
212
+ qgrid provider가 아닌 모델(google, openai 직접 호출)에서도 같은 request log 대시보드를 사용하려면 `createQgridLogger`를 `experimental_telemetry`에 넣으면 됩니다.
213
+
214
+ ```typescript
215
+ import { generateText } from "ai";
216
+ import { google } from "@ai-sdk/google";
217
+ import { createQgridLogger } from "@cartanova/qgrid-ai-sdk";
218
+
219
+ const { text } = await generateText({
220
+ model: google("gemini-3-flash"),
221
+ prompt: "안녕하세요",
222
+ experimental_telemetry: createQgridLogger({
223
+ serverUrl: "http://localhost:44900",
224
+ }),
225
+ });
226
+ ```
227
+
228
+ 병렬 호출, run 분리, telemetry 활성화 등은 자동 처리됩니다.
229
+
230
+ ### Logger 설정
231
+
232
+ ```typescript
233
+ createQgridLogger({
234
+ serverUrl: string; // qgrid 서버 주소 (필수)
235
+ projectName?: string; // request_logs.project_name (기본: QGRID_PROJECT_NAME 환경변수)
236
+ tokenName?: string; // request_logs.token_name (기본: "external")
237
+ staleRunTimeoutMs?: number; // watchdog timeout (기본: 30분 또는 AI SDK timeout + 여유시간, 0으로 비활성화)
238
+ onLogError?: (error: Error) => void; // 로깅 실패 콜백
239
+ });
240
+ ```
241
+
242
+ 모든 설정은 optional (serverUrl 제외). 기본값이 있으므로 `serverUrl`만 넣으면 동작합니다.
243
+
244
+ ### qgrid provider와 함께 사용
245
+
246
+ `qgrid()` provider는 자체 lifecycle이 있으므로 logger가 자동으로 suppress됩니다. 같은 코드에서 qgrid provider와 다른 provider를 섞어 써도 이중 기록되지 않습니다.
247
+
248
+ ## 지원 모델
249
+
250
+ ```typescript
251
+ type QgridSupportedModel =
252
+ // OpenAI (based on codex app-server)
253
+ | "openai/gpt-5.5"
254
+ | "openai/gpt-5.4"
255
+ | "openai/gpt-5.2"
256
+ | "openai/gpt-5.4-mini"
257
+ | "openai/gpt-5.3-codex"
258
+ | "openai/gpt-5.3-codex-spark"
259
+ // Anthropic
260
+ | "anthropic/claude-haiku-4-5"
261
+ | "anthropic/claude-sonnet-4"
262
+ | "anthropic/claude-sonnet-4-5"
263
+ | "anthropic/claude-sonnet-4-6"
264
+ | "anthropic/claude-sonnet-4-7"
265
+ | "anthropic/claude-sonnet-5"
266
+ | "anthropic/claude-opus-4"
267
+ | "anthropic/claude-opus-4-1"
268
+ | "anthropic/claude-opus-4-5"
269
+ | "anthropic/claude-opus-4-6"
270
+ | "anthropic/claude-opus-4-7"
271
+ | "anthropic/claude-opus-4-8"
272
+ ```
273
+
274
+ ## 설정
275
+
276
+ ```typescript
277
+ qgrid(modelId, {
278
+ serverUrl?: string; // qgrid 서버 주소 (기본: QGRID_URL 환경변수 또는 http://localhost:44900)
279
+ defaultEffort?: string; // effort 기본값 (기본: "low")
280
+ projectName?: string; // request_logs.project_name (기본: QGRID_PROJECT_NAME 환경변수)
281
+ });
282
+ ```
283
+
284
+ 여러 프로젝트/워크플로우가 한 qgrid 서버를 공유한다면 `QGRID_PROJECT_NAME`을 설정하세요. 대시보드에서 request log를 프로젝트별로 필터링하고 토큰/비용/캐시 지표를 워크로드별로 비교할 수 있습니다. config `projectName`은 특정 호출자만 다른 이름을 써야 할 때의 override 용도입니다.
285
+
286
+ ## 환경변수
287
+
288
+ | 변수 | 설명 | 기본값 |
289
+ |---|---|---|
290
+ | `QGRID_URL` | qgrid 서버 주소 | `http://localhost:44900` |
291
+ | `QGRID_PROJECT_NAME` | request log 프로젝트 이름 (provider/logger 공통) | (없음) |
292
+
293
+ ## 주의사항
294
+
295
+ - `temperature`, `maxOutputTokens` 등 sampling 파라미터는 CLI 런타임(OpenAI: codex app-server, Anthropic: Claude Code)이 지원하지 않아 무시됩니다.
296
+ - Structured output은 top-level `object` schema만 서버에서 강제됩니다. top-level `array`는 클라이언트 파싱 fallback.
297
+ - `tools`와 structured output schema는 서버에서 동시에 사용할 수 없습니다. tools가 있으면 schema는 서버로 전달되지 않고 최종 텍스트를 AI SDK가 클라이언트에서 파싱합니다.
298
+
299
+ ## 요구사항
300
+
301
+ - Node.js >= 20
302
+ - AI SDK v6 (`ai@^6.0.0`)
303
+ - 실행 중인 qgrid 서버
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # @cartanova/qgrid-ai-sdk
2
2
 
3
+ **English** · [한국어](./README.ko.md)
4
+
3
5
  AI SDK v6 custom `LanguageModelV3` provider for [qgrid](https://github.com/cartanova-ai/Qgrid).
4
6
 
5
- **기존 AI SDK 코드 변경 없이, `model` 한 줄만 바꾸면 qgrid를 통한 구독 토큰으로 N개(토큰개수) 병렬 풀링 + request log 대시보드를 사용가능**
7
+ **Without changing your existing AI SDK code, swap a single `model` line to get subscription-token pooling (N tokens × parallel workers) + the request log dashboard.**
6
8
 
7
9
  ```diff
8
10
  import { generateText } from "ai";
@@ -12,21 +14,21 @@ AI SDK v6 custom `LanguageModelV3` provider for [qgrid](https://github.com/carta
12
14
  const { text } = await generateText({
13
15
  - model: openai("gpt-5.4-mini"),
14
16
  + model: qgrid("openai/gpt-5.4-mini"),
15
- prompt: "서울 날씨 알려줘",
17
+ prompt: "What's the weather in Seoul?",
16
18
  });
17
19
  ```
18
20
 
19
- 이미 다른 provider(google, openai 등)를 직접 사용하고 있다면, **logger 옵션 한 줄**만 추가하면 에이전트의 매 step(generate, tool-call, reasoning)을 qgrid 대시보드에서 확인할 수 있습니다.
21
+ If you already use another provider (google, openai, ...) directly, add **a single logger line** to see every agent step (generate, tool-call, reasoning) in the qgrid dashboard.
20
22
 
21
23
  ```diff
22
24
  const { text } = await generateText({
23
25
  model: google("gemini-3-flash"),
24
- prompt: "복잡한 질문",
26
+ prompt: "A complex question",
25
27
  + experimental_telemetry: createQgridLogger({ serverUrl: "http://localhost:44900" }),
26
28
  });
27
29
  ```
28
30
 
29
- ## 설치
31
+ ## Install
30
32
 
31
33
  ```bash
32
34
  pnpm add @cartanova/qgrid-ai-sdk
@@ -34,7 +36,7 @@ pnpm add @cartanova/qgrid-ai-sdk
34
36
 
35
37
  Peer dependencies: `ai@^6.0.0`, `@ai-sdk/provider@^3.0.0`
36
38
 
37
- ## 빠른 시작
39
+ ## Quick start
38
40
 
39
41
  ```typescript
40
42
  import { generateText } from "ai";
@@ -42,16 +44,16 @@ import { qgrid } from "@cartanova/qgrid-ai-sdk";
42
44
 
43
45
  const { text } = await generateText({
44
46
  model: qgrid("openai/gpt-5.4-mini"),
45
- prompt: "서울 날씨 알려줘",
47
+ prompt: "What's the weather in Seoul?",
46
48
  });
47
49
  ```
48
50
 
49
- qgrid 서버(`http://localhost:44900`)가 실행 중이어야 합니다.
51
+ A qgrid server (`http://localhost:44900`) must be running.
50
52
 
51
- ## 사용법
52
- > 들어가기전에: 모든 클라언트 사용법은 [AI-SDK](https://ai-sdk.dev/docs/ai-sdk-core)와 동일합니다.
53
+ ## Usage
54
+ > Before you start: all client-side usage is identical to the [AI SDK](https://ai-sdk.dev/docs/ai-sdk-core).
53
55
 
54
- ### 텍스트 생성
56
+ ### Text generation
55
57
 
56
58
  ```typescript
57
59
  import { generateText } from "ai";
@@ -59,13 +61,13 @@ import { qgrid } from "@cartanova/qgrid-ai-sdk";
59
61
 
60
62
  const { text } = await generateText({
61
63
  model: qgrid("openai/gpt-5.4-mini"),
62
- system: "당신은 학술 논문 요약가입니다.",
64
+ system: "You are an academic paper summarizer.",
63
65
  prompt: paperText,
64
66
  });
65
67
  ```
66
68
 
67
- ### 구조화 응답 (Structured Output)
68
- > [AI-SDK structured output guide를 참조하세요](https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data)
69
+ ### Structured Output
70
+ > See the [AI SDK structured output guide](https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data)
69
71
 
70
72
  ```typescript
71
73
  import { generateText, Output } from "ai";
@@ -74,7 +76,7 @@ import { z } from "zod";
74
76
 
75
77
  const { output } = await generateText({
76
78
  model: qgrid("openai/gpt-5.4"),
77
- system: "논문 메타데이터를 추출해주세요.",
79
+ system: "Extract the paper metadata.",
78
80
  prompt: paperText,
79
81
  output: Output.object({
80
82
  schema: z.object({
@@ -88,10 +90,12 @@ const { output } = await generateText({
88
90
  console.log(output.title, output.authors);
89
91
  ```
90
92
 
91
- top-level이 `object`인 schema는 OpenAI structured output으로 서버에 전달됩니다 (임의로 타입 지정 시 파싱 에러)
92
- top-level이 AI-SDK의 가이드를 따르지않으면 AI SDK 클라이언트 파싱으로 fallback되며 경고 로그가 출력됩니다.
93
+ Schemas with a top-level `object` are forwarded to server-side structured output and enforced.
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
+
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.
93
97
 
94
- ### 스트리밍
98
+ ### Streaming
95
99
 
96
100
  ```typescript
97
101
  import { streamText } from "ai";
@@ -99,7 +103,7 @@ import { qgrid } from "@cartanova/qgrid-ai-sdk";
99
103
 
100
104
  const { textStream } = streamText({
101
105
  model: qgrid("openai/gpt-5.4-mini"),
102
- prompt: "TypeScript의 장점을 설명해줘",
106
+ prompt: "Explain the benefits of TypeScript",
103
107
  });
104
108
 
105
109
  for await (const chunk of textStream) {
@@ -116,54 +120,96 @@ import { z } from "zod";
116
120
 
117
121
  const { text } = await generateText({
118
122
  model: qgrid("openai/gpt-5.4-mini"),
119
- prompt: "서울 날씨 알려줘",
123
+ prompt: "What's the weather in Seoul?",
120
124
  tools: {
121
125
  getWeather: tool({
122
- description: "도시의 현재 날씨 조회",
126
+ description: "Get the current weather for a city",
123
127
  parameters: z.object({ city: z.string() }),
124
128
  execute: async ({ city }) => {
125
- return { temperature: 22, condition: "맑음" };
129
+ return { temperature: 22, condition: "sunny" };
126
130
  },
127
131
  }),
128
132
  },
129
133
  });
130
134
  ```
131
135
 
132
- tool-call은 qgrid 서버의 structured output emulation으로 동작합니다.
133
- AI SDK가 tool 실행을 관리하고, qgrid는 각 턴의 LLM 호출만 담당합니다.
136
+ Tool calls work through the qgrid server's structured output emulation.
137
+ The AI SDK manages tool execution; qgrid handles only each turn's LLM call.
134
138
 
135
139
  ### Provider Options
136
140
 
141
+ All qgrid-specific options go under the `providerOptions.qgrid` namespace. (Not `providerOptions.openai`.)
142
+
137
143
  ```typescript
138
144
  import { generateText } from "ai";
139
145
  import { qgrid, type QgridProviderOptions } from "@cartanova/qgrid-ai-sdk";
140
146
 
141
- const providerOptions = {
142
- openai: {
143
- reasoningEffort: "high",
144
- reasoningSummary: "concise",
145
- textVerbosity: "medium",
147
+ const { text } = await generateText({
148
+ model: qgrid("openai/gpt-5.4"),
149
+ prompt: "Analyze this complex problem",
150
+ providerOptions: {
151
+ qgrid: {
152
+ effort: "high",
153
+ reasoningSummary: "concise",
154
+ verbosity: "medium",
155
+ } satisfies QgridProviderOptions,
146
156
  },
147
- } satisfies QgridProviderOptions;
157
+ });
158
+ ```
159
+
160
+ | Option | Values | Applies to | Description |
161
+ |---|---|---|---|
162
+ | `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
+ | `effort` | `"none"` \| `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` | both providers | Reasoning depth. Defaults to the config's `defaultEffort` (`"low"`) |
164
+ | `verbosity` | `"low"` \| `"medium"` \| `"high"` | OpenAI only | Response text verbosity |
165
+ | `reasoningSummary` | `"auto"` \| `"concise"` \| `"detailed"` \| `"none"` | OpenAI only | Reasoning summary output mode |
166
+ | `serviceTier` | `string` | OpenAI only | OpenAI/codex service tier |
167
+ | `imageGeneration` | `boolean` | OpenAI only, non-stream | Enables codex's built-in `image_generation` tool (see [below](#image-generation)) |
168
+ | `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 |
170
+
171
+ ### Multi-turn prompt cache (sessionKey)
148
172
 
173
+ 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.
174
+
175
+ ```typescript
149
176
  const { text } = await generateText({
150
- model: qgrid("openai/gpt-5.4"),
151
- prompt: "복잡한 문제를 분석해줘",
152
- providerOptions,
177
+ model: qgrid("openai/gpt-5.4-mini"),
178
+ prompt: nextTurnPrompt,
179
+ providerOptions: { qgrid: { sessionKey: "game-session-123" } },
153
180
  });
154
181
  ```
155
182
 
156
- | 옵션 | 값 | 설명 |
157
- |---|---|---|
158
- | `reasoningEffort` | `"none"` \| `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` | reasoning 모델의 추론 깊이 |
159
- | `reasoningSummary` | `"auto"` \| `"concise"` \| `"detailed"` \| `"none"` | 추론 요약 출력 방식 (Responses API 전용) |
160
- | `textVerbosity` | `"low"` \| `"medium"` \| `"high"` | 응답 텍스트의 상세도 |
183
+ - Threads expire after 10 minutes idle. On expiry or worker restart, the request automatically starts a cold thread instead of failing.
184
+ - Ignored for `anthropic/*` models (Claude Code spawns a fresh process per request, so there is no thread reuse).
185
+ - Can be disabled server-side with `QGRID_OPENAI_THREAD_REUSE=false`.
186
+
187
+ ### Image Generation
188
+
189
+ OpenAI/codex route only, `generateText` only. Enables codex's built-in `image_generation` tool for that single request and returns the image through the AI SDK `files`.
190
+
191
+ ```typescript
192
+ const result = await generateText({
193
+ model: qgrid("openai/gpt-5.4"),
194
+ prompt: "An illustration of a whale flying through space",
195
+ providerOptions: {
196
+ qgrid: {
197
+ imageGeneration: true,
198
+ imageGenerationOptions: { quality: "medium", size: "1536x1024" },
199
+ },
200
+ },
201
+ });
161
202
 
162
- codex app-server가 지원하는 옵션만 포함되어 있습니다. `temperature`, `maxOutputTokens` 등은 codex가 무시합니다.
203
+ const image = result.files[0]; // mediaType: "image/png", base64
204
+ ```
205
+
206
+ - Rejected in `streamText` (non-stream only).
207
+ - Image-generation requests always run on a cold one-shot thread and are excluded from thread reuse.
208
+ - The image cost is an **estimate** based on the public `gpt-image-2` price table, recorded separately as `image_cost_usd` on the request log (codex does not expose exact image-tool usage).
163
209
 
164
210
  ## Telemetry Logger
165
211
 
166
- qgrid provider가 아닌 모델(google, openai 직접 호출)에서도 같은 request log 대시보드를 사용하려면 `createQgridLogger`를 `experimental_telemetry`에 넣으면 됩니다.
212
+ To use the same request log dashboard with models that don't go through the qgrid provider (direct google/openai calls), pass `createQgridLogger` to `experimental_telemetry`.
167
213
 
168
214
  ```typescript
169
215
  import { generateText } from "ai";
@@ -172,34 +218,34 @@ import { createQgridLogger } from "@cartanova/qgrid-ai-sdk";
172
218
 
173
219
  const { text } = await generateText({
174
220
  model: google("gemini-3-flash"),
175
- prompt: "안녕하세요",
221
+ prompt: "Hello",
176
222
  experimental_telemetry: createQgridLogger({
177
223
  serverUrl: "http://localhost:44900",
178
224
  }),
179
225
  });
180
226
  ```
181
227
 
182
- 병렬 호출, run 분리, telemetry 활성화 등은 자동 처리됩니다.
228
+ Parallel calls, run separation, and telemetry activation are handled automatically.
183
229
 
184
- ### Logger 설정
230
+ ### Logger configuration
185
231
 
186
232
  ```typescript
187
233
  createQgridLogger({
188
- serverUrl: string; // qgrid 서버 주소 (필수)
189
- projectName?: string; // request_logs.project_name
190
- tokenName?: string; // request_logs.token_name (기본: "external")
191
- staleRunTimeoutMs?: number; // watchdog timeout (기본: 30분, 0으로 비활성화)
192
- onLogError?: (error: Error) => void; // 로깅 실패 콜백
234
+ serverUrl: string; // qgrid server address (required)
235
+ projectName?: string; // request_logs.project_name (default: QGRID_PROJECT_NAME env var)
236
+ tokenName?: string; // request_logs.token_name (default: "external")
237
+ staleRunTimeoutMs?: number; // watchdog timeout (default: 30 min, or the AI SDK timeout plus a grace period; 0 disables)
238
+ onLogError?: (error: Error) => void; // logging-failure callback
193
239
  });
194
240
  ```
195
241
 
196
- 모든 설정은 optional (serverUrl 제외). 기본값이 있으므로 `serverUrl`만 넣으면 동작합니다.
242
+ Everything except `serverUrl` is optional. With defaults in place, passing only `serverUrl` works.
197
243
 
198
- ### qgrid provider와 함께 사용
244
+ ### Using alongside the qgrid provider
199
245
 
200
- `qgrid()` provider는 자체 lifecycle이 있으므로 logger가 자동으로 suppress됩니다. 같은 코드에서 qgrid provider와 다른 provider를 섞어 써도 이중 기록되지 않습니다.
246
+ 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.
201
247
 
202
- ## 지원 모델
248
+ ## Supported models
203
249
 
204
250
  ```typescript
205
251
  type QgridSupportedModel =
@@ -216,6 +262,7 @@ type QgridSupportedModel =
216
262
  | "anthropic/claude-sonnet-4-5"
217
263
  | "anthropic/claude-sonnet-4-6"
218
264
  | "anthropic/claude-sonnet-4-7"
265
+ | "anthropic/claude-sonnet-5"
219
266
  | "anthropic/claude-opus-4"
220
267
  | "anthropic/claude-opus-4-1"
221
268
  | "anthropic/claude-opus-4-5"
@@ -224,28 +271,33 @@ type QgridSupportedModel =
224
271
  | "anthropic/claude-opus-4-8"
225
272
  ```
226
273
 
227
- ## 설정
274
+ ## Configuration
228
275
 
229
276
  ```typescript
230
277
  qgrid(modelId, {
231
- serverUrl?: string; // qgrid 서버 주소 (기본: QGRID_URL 환경변수 또는 http://localhost:44900)
232
- defaultEffort?: string; // reasoningEffort 기본값 (기본: "low")
278
+ serverUrl?: string; // qgrid server address (default: QGRID_URL env var, then http://localhost:44900)
279
+ defaultEffort?: string; // default effort (default: "low")
280
+ projectName?: string; // request_logs.project_name (default: QGRID_PROJECT_NAME env var)
233
281
  });
234
282
  ```
235
283
 
236
- ## 환경변수
284
+ If multiple projects/workflows share one qgrid server, set `QGRID_PROJECT_NAME`. It lets you filter request logs by project in the dashboard and compare token/cost/cache metrics per workload. The config-level `projectName` is an override for callers that need a different label.
285
+
286
+ ## Environment variables
237
287
 
238
- | 변수 | 설명 | 기본값 |
288
+ | Variable | Description | Default |
239
289
  |---|---|---|
240
- | `QGRID_URL` | qgrid 서버 주소 | `http://localhost:44900` |
290
+ | `QGRID_URL` | qgrid server address | `http://localhost:44900` |
291
+ | `QGRID_PROJECT_NAME` | request log project name (provider and logger) | (empty) |
241
292
 
242
- ## 주의사항
293
+ ## Notes
243
294
 
244
- - `temperature`, `maxOutputTokens` 등 sampling 파라미터는 codex app-server가 지원하지 않아 무시됩니다.
245
- - Structured output은 top-level `object` schema만 지원합니다. top-level `array`는 클라이언트 파싱 fallback.
295
+ - Sampling parameters such as `temperature` and `maxOutputTokens` are ignored — the CLI runtimes (OpenAI: codex app-server, Anthropic: Claude Code) do not support them.
296
+ - Structured output is server-enforced only for top-level `object` schemas. Top-level `array` falls back to client-side parsing.
297
+ - `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.
246
298
 
247
- ## 요구사항
299
+ ## Requirements
248
300
 
249
301
  - Node.js >= 20
250
302
  - AI SDK v6 (`ai@^6.0.0`)
251
- - 실행 중인 qgrid 서버
303
+ - A running qgrid server
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-ai-sdk",
3
- "version": "2.3.8",
3
+ "version": "2.3.9",
4
4
  "description": "AI SDK LanguageModelV3 provider for qgrid",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/cartanova-ai/qgrid"
8
8
  },
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "README.ko.md"
11
12
  ],
12
13
  "type": "module",
13
14
  "main": "dist/index.js",