@cartanova/qgrid-ai-sdk 2.7.7 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.ko.md CHANGED
@@ -201,6 +201,7 @@ const { text } = await generateText({
201
201
 
202
202
  | 옵션 | 값 | 적용 범위 | 설명 |
203
203
  |---|---|---|---|
204
+ | `tokenName` | provider prefix를 포함한 토큰 이름 | 공통 | `anthropic/yds`처럼 활성 토큰 하나를 엄격히 지정. prefix는 model provider와 같아야 하며 빈 값·누락·inactive·quota 초과 시 다른 토큰으로 fallback하지 않음 |
204
205
  | `logger` | `boolean` | 공통 | qgrid request log 저장 여부. 기본값은 `true`. `false`로 설정해도 client tool 실행과 multi-step 연결은 계속 동작 |
205
206
  | `sessionKey` | `string` | OpenAI 전용 | 전체 history 재전송 시 불투명 prompt-cache affinity를 파생하는 멀티턴 대화 식별자 ([아래](#멀티턴-prompt-cache-sessionkey) 참조) |
206
207
  | `effort` | `"none"` \| `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` \| `"max"` | 공통 (지원 값은 모델별 상이, 예: `"max"`는 GPT-5.6+) | reasoning 모델의 추론 깊이. 기본값은 config의 `defaultEffort` (`"low"`) |
@@ -212,6 +213,18 @@ const { text } = await generateText({
212
213
  | `imageGenerationOptions` | `{ quality?, size? }` | OpenAI 전용 | 이미지 품질/크기 힌트. `quality: "low" \| "medium" \| "high"`, `size: "1024x1024" \| "1024x1536" \| "1536x1024"` (기본: `medium` / `1536x1024`) |
213
214
  | `fallbackModels` | `string[]` | 예약 | 향후 qgrid 서버 fallback routing용 예약 필드. 현재 동작하지 않으며 Claude Code의 Fable refusal fallback과 무관 |
214
215
 
216
+ ```typescript
217
+ await generateText({
218
+ model: qgrid("anthropic/claude-fable-5"),
219
+ prompt,
220
+ providerOptions: {
221
+ qgrid: { tokenName: "anthropic/yds" } satisfies QgridProviderOptions,
222
+ },
223
+ });
224
+ ```
225
+
226
+ `tokenName`은 `streamText`에서도 같은 방식으로 동작합니다.
227
+
215
228
  AI SDK 최상위 `timeout`은 전체 클라이언트 제한시간이며 custom provider 실행 전에
216
229
  `AbortSignal`로 변환됩니다. 따라서 그 숫자 자체는 qgrid에 전달되지 않습니다. qgrid 서버의
217
230
  Claude Code 프로세스 제한시간을 바꾸려면 `providerOptions.qgrid.timeoutMs`를 사용하세요.
package/README.md CHANGED
@@ -210,6 +210,7 @@ const { text } = await generateText({
210
210
 
211
211
  | Option | Values | Applies to | Description |
212
212
  |---|---|---|---|
213
+ | `tokenName` | provider-prefixed token name | both providers | Strictly targets one active token, such as `anthropic/yds`. The prefix must match the model provider; empty, missing, inactive, or over-threshold targets fail without fallback |
213
214
  | `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 |
214
215
  | `sessionKey` | `string` | OpenAI only | Multi-turn conversation identifier used to derive opaque prompt-cache affinity while replaying full history (see [below](#multi-turn-prompt-cache-sessionkey)) |
215
216
  | `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"`) |
@@ -221,6 +222,18 @@ const { text } = await generateText({
221
222
  | `imageGenerationOptions` | `{ quality?, size? }` | OpenAI only | Image quality/size hints. `quality: "low" \| "medium" \| "high"`, `size: "1024x1024" \| "1024x1536" \| "1536x1024"` (defaults: `medium` / `1536x1024`) |
222
223
  | `fallbackModels` | `string[]` | reserved | Reserved for future qgrid server-side fallback routing. Not functional yet and unrelated to Claude Code's Fable refusal fallback |
223
224
 
225
+ ```typescript
226
+ await generateText({
227
+ model: qgrid("anthropic/claude-fable-5"),
228
+ prompt,
229
+ providerOptions: {
230
+ qgrid: { tokenName: "anthropic/yds" } satisfies QgridProviderOptions,
231
+ },
232
+ });
233
+ ```
234
+
235
+ `tokenName` works the same way with `streamText`.
236
+
224
237
  AI SDK's top-level `timeout` remains the overall client-side budget and is converted to an
225
238
  `AbortSignal` before the custom provider runs. It does not expose the numeric timeout to qgrid.
226
239
  Use `providerOptions.qgrid.timeoutMs` when the qgrid server's Claude Code process needs a different
package/dist/index.d.ts CHANGED
@@ -12,6 +12,10 @@ type QgridProviderConfig = {
12
12
  * providerOptions.qgrid 로 전달한다.
13
13
  */
14
14
  type QgridProviderOptions = {
15
+ /**
16
+ * 이 요청을 처리할 활성 qgrid 토큰 이름. provider prefix를 포함해야 하며 다른 토큰으로 fallback하지 않는다.
17
+ */
18
+ tokenName?: string;
15
19
  /**
16
20
  * qgrid request log 저장 여부. 기본값은 true.
17
21
  * false여도 client tool 실행과 multi-step 연결은 계속 동작한다.
package/dist/index.js CHANGED
@@ -693,6 +693,14 @@ const DEFAULT_RUN_KEY = "__qgrid_default_run__";
693
693
  const DEFAULT_STALE_RUN_TIMEOUT_MS = 1800 * 1e3;
694
694
  const STALE_RUN_GRACE_MS = 5e3;
695
695
  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.";
696
+ function isParseableJson(value) {
697
+ try {
698
+ JSON.parse(value ?? "");
699
+ return true;
700
+ } catch {
701
+ return false;
702
+ }
703
+ }
696
704
  function fullModelName(provider, modelId) {
697
705
  if (!provider || !modelId) return void 0;
698
706
  const prefix = `${provider.split(".", 1)[0] ?? provider}/`;
@@ -764,6 +772,7 @@ function createQgridLogger(config = {}) {
764
772
  totalCacheReadTokens: result.totalUsage?.inputTokenDetails?.cacheReadTokens ?? 0,
765
773
  totalCacheCreationTokens: result.totalUsage?.inputTokenDetails?.cacheWriteTokens ?? 0,
766
774
  totalDurationMs: Date.now() - run.startTime,
775
+ ...run.isStructured && result.status === "succeeded" ? { responseJsonOk: isParseableJson(result.response) } : {},
767
776
  ...result.errorMessage ? { errorMessage: result.errorMessage } : {}
768
777
  }).catch((e) => onLogError(e instanceof Error ? e : new Error(String(e))));
769
778
  };
@@ -814,12 +823,17 @@ function createQgridLogger(config = {}) {
814
823
  try {
815
824
  const messages = event.messages ?? (Array.isArray(event.prompt) ? event.prompt : void 0);
816
825
  const history = serializeHistory(messages);
826
+ const responseFormat = event.output ? await event.output.responseFormat : void 0;
827
+ const isStructured = responseFormat?.type === "json";
828
+ const jsonSchema = isStructured && responseFormat.schema !== void 0 ? JSON.stringify(responseFormat.schema) ?? null : null;
817
829
  const result = await createRun(serverUrl, {
818
830
  userPrompt: extractUserPrompt(event.prompt, messages),
819
831
  systemPrompt: extractSystemPrompt(event.system),
820
832
  modelName: requestedModelName,
821
833
  projectName,
822
- history
834
+ history,
835
+ isStructured,
836
+ jsonSchema
823
837
  });
824
838
  if (reservation.errorMessage || quarantined.has(runKey) || starting.get(runKey) !== reservation) {
825
839
  if (starting.get(runKey) === reservation) starting.delete(runKey);
@@ -873,6 +887,7 @@ function createQgridLogger(config = {}) {
873
887
  runs.set(runKey, {
874
888
  requestLogId: result.requestLogId,
875
889
  requestedModelName,
890
+ isStructured,
876
891
  pendingSteps: [],
877
892
  pendingToolCalls: [],
878
893
  startTime: Date.now(),
@@ -1060,7 +1075,9 @@ function qgridProviderMetadata(data) {
1060
1075
  } };
1061
1076
  }
1062
1077
  function getQgridProviderOptions(options) {
1063
- return options.providerOptions?.qgrid ?? {};
1078
+ const qgridOptions = options.providerOptions?.qgrid ?? {};
1079
+ if (qgridOptions.tokenName !== void 0 && qgridOptions.tokenName.trim() === "") throw new Error("qgrid: tokenName must not be empty");
1080
+ return qgridOptions;
1064
1081
  }
1065
1082
  function toAiSdkUsage(usage) {
1066
1083
  const cacheRead = usage.cache_read_input_tokens;
@@ -1135,6 +1152,7 @@ function qgrid(modelId, config) {
1135
1152
  const serviceTier = qgridOptions.serviceTier;
1136
1153
  const timeoutMs = qgridOptions.timeoutMs;
1137
1154
  const logger = qgridOptions.logger;
1155
+ const tokenName = qgridOptions.tokenName;
1138
1156
  const imageGenerationOptions = qgridOptions.imageGenerationOptions;
1139
1157
  if (imageGeneration) assertImageInputFitsJsonTransport(imageUrls);
1140
1158
  const sessionKey = qgridOptions.sessionKey;
@@ -1169,6 +1187,7 @@ function qgrid(modelId, config) {
1169
1187
  ...projectName ? { projectName } : {},
1170
1188
  ...cacheAffinityKey ? { cacheAffinityKey } : {},
1171
1189
  ...logger === false ? { logger: false } : {},
1190
+ ...tokenName ? { tokenName } : {},
1172
1191
  ...runContext ? { runContext } : {},
1173
1192
  ...toolResultsPayload ? { toolResults: toolResultsPayload } : {},
1174
1193
  ...imageGeneration ? { imageGeneration } : {},
@@ -1239,6 +1258,7 @@ function qgrid(modelId, config) {
1239
1258
  const serviceTier = qgridOptions.serviceTier;
1240
1259
  const timeoutMs = qgridOptions.timeoutMs;
1241
1260
  const logger = qgridOptions.logger;
1261
+ const tokenName = qgridOptions.tokenName;
1242
1262
  if (qgridOptions.imageGeneration) throw new Error("qgrid: imageGeneration is not supported with streamText — use generateText instead.");
1243
1263
  const { prompt, system, history, droppedImageCount } = extractPromptAndHistory(options.prompt, { includeImages: false });
1244
1264
  warnDroppedImages(droppedImageCount);
@@ -1272,6 +1292,7 @@ function qgrid(modelId, config) {
1272
1292
  ...projectName ? { projectName } : {},
1273
1293
  ...cacheAffinityKey ? { cacheAffinityKey } : {},
1274
1294
  ...logger === false ? { logger: false } : {},
1295
+ ...tokenName ? { tokenName } : {},
1275
1296
  ...runContext ? { runContext } : {},
1276
1297
  ...toolResultsPayload ? { toolResults: toolResultsPayload } : {}
1277
1298
  } }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-ai-sdk",
3
- "version": "2.7.7",
3
+ "version": "2.8.0",
4
4
  "description": "AI SDK LanguageModelV3 provider for qgrid",
5
5
  "repository": {
6
6
  "type": "git",