@cartanova/qgrid-ai-sdk 2.4.1 → 2.4.3
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 +14 -1
- package/README.md +14 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +52 -44
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -160,7 +160,7 @@ const { text } = await generateText({
|
|
|
160
160
|
| 옵션 | 값 | 적용 범위 | 설명 |
|
|
161
161
|
|---|---|---|---|
|
|
162
162
|
| `sessionKey` | `string` | OpenAI 전용 | 멀티턴 대화 식별자. 같은 key는 같은 codex thread로 라우팅되어 prompt cache 적중 ([아래](#멀티턴-prompt-cache-sessionkey) 참조) |
|
|
163
|
-
| `effort` | `"none"` \| `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` | 공통 | reasoning 모델의 추론 깊이. 기본값은 config의 `defaultEffort` (`"low"`) |
|
|
163
|
+
| `effort` | `"none"` \| `"minimal"` \| `"low"` \| `"medium"` \| `"high"` \| `"xhigh"` \| `"max"` | 공통 (지원 값은 모델별 상이, 예: `"max"`는 GPT-5.6+) | reasoning 모델의 추론 깊이. 기본값은 config의 `defaultEffort` (`"low"`) |
|
|
164
164
|
| `verbosity` | `"low"` \| `"medium"` \| `"high"` | OpenAI 전용 | 응답 텍스트의 상세도 |
|
|
165
165
|
| `reasoningSummary` | `"auto"` \| `"concise"` \| `"detailed"` \| `"none"` | OpenAI 전용 | 추론 요약 출력 방식 |
|
|
166
166
|
| `serviceTier` | `string` | OpenAI 전용 | OpenAI/codex service tier |
|
|
@@ -269,6 +269,9 @@ createQgridLogger({
|
|
|
269
269
|
```typescript
|
|
270
270
|
type QgridSupportedModel =
|
|
271
271
|
// OpenAI (based on codex app-server)
|
|
272
|
+
| "openai/gpt-5.6-sol"
|
|
273
|
+
| "openai/gpt-5.6-terra"
|
|
274
|
+
| "openai/gpt-5.6-luna"
|
|
272
275
|
| "openai/gpt-5.5"
|
|
273
276
|
| "openai/gpt-5.4"
|
|
274
277
|
| "openai/gpt-5.2"
|
|
@@ -290,6 +293,16 @@ type QgridSupportedModel =
|
|
|
290
293
|
| "anthropic/claude-opus-4-8"
|
|
291
294
|
```
|
|
292
295
|
|
|
296
|
+
### GPT-5.6 사양
|
|
297
|
+
|
|
298
|
+
| 모델 | Context (qgrid/codex 런타임) | 최대 출력 | 1M tokens당 input / cached input / output |
|
|
299
|
+
|---|---:|---:|---:|
|
|
300
|
+
| `openai/gpt-5.6-sol` | 372K | 128K | $5 / $0.50 / $30 |
|
|
301
|
+
| `openai/gpt-5.6-terra` | 372K | 128K | $2.50 / $0.25 / $15 |
|
|
302
|
+
| `openai/gpt-5.6-luna` | 372K | 128K | $1 / $0.10 / $6 |
|
|
303
|
+
|
|
304
|
+
세 모델 모두 `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
|
+
|
|
293
306
|
## 설정
|
|
294
307
|
|
|
295
308
|
```typescript
|
package/README.md
CHANGED
|
@@ -160,7 +160,7 @@ const { text } = await generateText({
|
|
|
160
160
|
| Option | Values | Applies to | Description |
|
|
161
161
|
|---|---|---|---|
|
|
162
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"`) |
|
|
163
|
+
| `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
164
|
| `verbosity` | `"low"` \| `"medium"` \| `"high"` | OpenAI only | Response text verbosity |
|
|
165
165
|
| `reasoningSummary` | `"auto"` \| `"concise"` \| `"detailed"` \| `"none"` | OpenAI only | Reasoning summary output mode |
|
|
166
166
|
| `serviceTier` | `string` | OpenAI only | OpenAI/codex service tier |
|
|
@@ -269,6 +269,9 @@ The `qgrid()` provider has its own lifecycle, so the logger automatically suppre
|
|
|
269
269
|
```typescript
|
|
270
270
|
type QgridSupportedModel =
|
|
271
271
|
// OpenAI (based on codex app-server)
|
|
272
|
+
| "openai/gpt-5.6-sol"
|
|
273
|
+
| "openai/gpt-5.6-terra"
|
|
274
|
+
| "openai/gpt-5.6-luna"
|
|
272
275
|
| "openai/gpt-5.5"
|
|
273
276
|
| "openai/gpt-5.4"
|
|
274
277
|
| "openai/gpt-5.2"
|
|
@@ -290,6 +293,16 @@ type QgridSupportedModel =
|
|
|
290
293
|
| "anthropic/claude-opus-4-8"
|
|
291
294
|
```
|
|
292
295
|
|
|
296
|
+
### GPT-5.6 specifications
|
|
297
|
+
|
|
298
|
+
| Model | Context (qgrid/codex runtime) | Max output | Input / cached input / output per 1M tokens |
|
|
299
|
+
|---|---:|---:|---:|
|
|
300
|
+
| `openai/gpt-5.6-sol` | 372K | 128K | $5 / $0.50 / $30 |
|
|
301
|
+
| `openai/gpt-5.6-terra` | 372K | 128K | $2.50 / $0.25 / $15 |
|
|
302
|
+
| `openai/gpt-5.6-luna` | 372K | 128K | $1 / $0.10 / $6 |
|
|
303
|
+
|
|
304
|
+
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
|
+
|
|
293
306
|
## Configuration
|
|
294
307
|
|
|
295
308
|
```typescript
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ type QgridProviderOptions = {
|
|
|
20
20
|
*/
|
|
21
21
|
sessionKey?: string;
|
|
22
22
|
/** reasoning 모델의 추론 깊이. 기본값은 qgrid config의 defaultEffort. */
|
|
23
|
-
effort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
23
|
+
effort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
24
24
|
/** 응답 텍스트의 상세도. OpenAI/codex route에서만 적용된다. */
|
|
25
25
|
verbosity?: "low" | "medium" | "high";
|
|
26
26
|
/** reasoning 모델의 추론 요약 출력 방식. OpenAI/codex route에서만 적용된다. */
|
|
@@ -50,7 +50,7 @@ type QgridProviderOptions = {
|
|
|
50
50
|
* provider 내부 threadCoordStore 가 sessionKey 별로 관리
|
|
51
51
|
*/
|
|
52
52
|
|
|
53
|
-
type QgridSupportedModel = "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";
|
|
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
54
|
type QgridLoggerConfig = {
|
|
55
55
|
/** qgrid 서버 주소. default: process.env.QGRID_URL, 없으면 기본값은 "http://localhost:44900" */
|
|
56
56
|
serverUrl?: string;
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ const QGRID_PROVIDER_NAME = "qgrid";
|
|
|
3
3
|
const THREAD_COORD_TTL_MS = 10 * 6e4;
|
|
4
4
|
const TEXT_STREAM_ID_PREFIX = "text";
|
|
5
5
|
const TOP_LEVEL_SCHEMA_WARNING = "OpenAI structured output requires \"object\". Falling back to client-side parsing.";
|
|
6
|
-
const PENDING_TOOL_RESULTS_WARNING = "[qgrid]
|
|
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
|
|
9
9
|
async function createRun(serverUrl, body) {
|
|
@@ -602,16 +602,16 @@ function createQgridLogger(config = {}) {
|
|
|
602
602
|
//#endregion
|
|
603
603
|
//#region src/index.ts
|
|
604
604
|
const threadCoordStore = /* @__PURE__ */ new Map();
|
|
605
|
-
const MAX_IMAGE_INPUT_DATA_URL_CHARS =
|
|
606
|
-
const MAX_IMAGE_INPUT_DATA_URL_CHARS_LABEL =
|
|
605
|
+
const MAX_IMAGE_INPUT_DATA_URL_CHARS = 9e6;
|
|
606
|
+
const MAX_IMAGE_INPUT_DATA_URL_CHARS_LABEL = "9M";
|
|
607
607
|
function assertImageInputFitsJsonTransport(imageUrls) {
|
|
608
608
|
let totalDataUrlChars = 0;
|
|
609
609
|
for (const url of imageUrls) {
|
|
610
610
|
if (!url.toLowerCase().startsWith("data:")) continue;
|
|
611
611
|
totalDataUrlChars += url.length;
|
|
612
|
-
if (url.length > MAX_IMAGE_INPUT_DATA_URL_CHARS) throw new Error(`qgrid:
|
|
612
|
+
if (url.length > MAX_IMAGE_INPUT_DATA_URL_CHARS) throw new Error(`qgrid: image input is too large for JSON transport (${url.length} chars). Compress or resize it before passing it to imageGeneration, for example as WebP/JPEG under ${MAX_IMAGE_INPUT_DATA_URL_CHARS_LABEL} total base64 data-url chars.`);
|
|
613
613
|
}
|
|
614
|
-
if (totalDataUrlChars > MAX_IMAGE_INPUT_DATA_URL_CHARS) throw new Error(`qgrid:
|
|
614
|
+
if (totalDataUrlChars > MAX_IMAGE_INPUT_DATA_URL_CHARS) throw new Error(`qgrid: image inputs are too large for JSON transport (${totalDataUrlChars} total chars). Compress or resize them before passing them to imageGeneration, for example as WebP/JPEG under ${MAX_IMAGE_INPUT_DATA_URL_CHARS_LABEL} total base64 data-url chars.`);
|
|
615
615
|
}
|
|
616
616
|
function warnDroppedImages(droppedImageCount) {
|
|
617
617
|
if (droppedImageCount <= 0) return;
|
|
@@ -659,7 +659,39 @@ function qgrid(modelId, config) {
|
|
|
659
659
|
const serverUrl = config?.serverUrl ?? process.env.QGRID_URL ?? "http://localhost:44900";
|
|
660
660
|
const effort = config?.defaultEffort ?? "low";
|
|
661
661
|
const projectName = config?.projectName ?? process.env.QGRID_PROJECT_NAME;
|
|
662
|
-
|
|
662
|
+
const clientRuns = /* @__PURE__ */ new Map();
|
|
663
|
+
let anonymousClientRunId = 0;
|
|
664
|
+
function clientRunKey(runContext) {
|
|
665
|
+
if (runContext.requestLogId !== void 0) return `request:${runContext.requestLogId}`;
|
|
666
|
+
if (runContext.threadCoord) {
|
|
667
|
+
const { workerId, epoch, threadId } = runContext.threadCoord;
|
|
668
|
+
return `thread:${workerId}:${epoch}:${threadId}`;
|
|
669
|
+
}
|
|
670
|
+
anonymousClientRunId++;
|
|
671
|
+
return `anonymous:${anonymousClientRunId}`;
|
|
672
|
+
}
|
|
673
|
+
function rememberClientRun(runContext, toolCallIds) {
|
|
674
|
+
if (toolCallIds.length === 0) return;
|
|
675
|
+
clientRuns.set(clientRunKey(runContext), {
|
|
676
|
+
runContext,
|
|
677
|
+
pendingToolCallIds: new Set(toolCallIds)
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
function matchClientRun(messages) {
|
|
681
|
+
const toolResults = extractToolResultsFromHistory(messages);
|
|
682
|
+
if (toolResults.length === 0) return null;
|
|
683
|
+
const resultIds = new Set(toolResults.map((r) => r.callId));
|
|
684
|
+
for (const [key, run] of clientRuns) if (run.pendingToolCallIds.size > 0 && [...run.pendingToolCallIds].every((id) => resultIds.has(id))) return {
|
|
685
|
+
key,
|
|
686
|
+
runContext: run.runContext,
|
|
687
|
+
toolResultsPayload: toolResults.filter((r) => run.pendingToolCallIds.has(r.callId)).map((r) => ({
|
|
688
|
+
toolCallId: r.callId,
|
|
689
|
+
output: r.result
|
|
690
|
+
}))
|
|
691
|
+
};
|
|
692
|
+
if (clientRuns.size > 0) console.warn(PENDING_TOOL_RESULTS_WARNING);
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
663
695
|
return {
|
|
664
696
|
specificationVersion: "v3",
|
|
665
697
|
provider: QGRID_PROVIDER_NAME,
|
|
@@ -687,20 +719,11 @@ function qgrid(modelId, config) {
|
|
|
687
719
|
let runContext;
|
|
688
720
|
let toolResultsPayload;
|
|
689
721
|
let logMode;
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
toolResultsPayload = toolResults.filter((r) => clientRun.pendingToolCallIds.has(r.callId)).map((r) => ({
|
|
696
|
-
toolCallId: r.callId,
|
|
697
|
-
output: r.result
|
|
698
|
-
}));
|
|
699
|
-
logMode = "run";
|
|
700
|
-
} else {
|
|
701
|
-
console.warn(PENDING_TOOL_RESULTS_WARNING);
|
|
702
|
-
clientRun = null;
|
|
703
|
-
}
|
|
722
|
+
const matchedClientRun = matchClientRun(options.prompt);
|
|
723
|
+
if (matchedClientRun) {
|
|
724
|
+
runContext = matchedClientRun.runContext;
|
|
725
|
+
toolResultsPayload = matchedClientRun.toolResultsPayload;
|
|
726
|
+
logMode = "run";
|
|
704
727
|
}
|
|
705
728
|
const storedCoord = reuseSessionKey ? getThreadCoord(reuseSessionKey) : void 0;
|
|
706
729
|
if (!runContext && storedCoord) runContext = { threadCoord: storedCoord };
|
|
@@ -765,11 +788,8 @@ function qgrid(modelId, config) {
|
|
|
765
788
|
text: data.text
|
|
766
789
|
});
|
|
767
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.");
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
pendingToolCallIds: new Set(content.filter((c) => c.type === "tool-call").map((c) => c.toolCallId))
|
|
771
|
-
};
|
|
772
|
-
else clientRun = null;
|
|
791
|
+
if (matchedClientRun) clientRuns.delete(matchedClientRun.key);
|
|
792
|
+
if (data.runContext && finishReason.unified === "tool-calls") rememberClientRun(data.runContext, content.filter((c) => c.type === "tool-call").map((c) => c.toolCallId));
|
|
773
793
|
if (reuseSessionKey && data.runContext?.threadCoord) setThreadCoord(reuseSessionKey, data.runContext.threadCoord);
|
|
774
794
|
return {
|
|
775
795
|
content,
|
|
@@ -803,20 +823,11 @@ function qgrid(modelId, config) {
|
|
|
803
823
|
let runContext;
|
|
804
824
|
let toolResultsPayload;
|
|
805
825
|
let logMode;
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
toolResultsPayload = toolResults.filter((r) => clientRun.pendingToolCallIds.has(r.callId)).map((r) => ({
|
|
812
|
-
toolCallId: r.callId,
|
|
813
|
-
output: r.result
|
|
814
|
-
}));
|
|
815
|
-
logMode = "run";
|
|
816
|
-
} else {
|
|
817
|
-
console.warn(PENDING_TOOL_RESULTS_WARNING);
|
|
818
|
-
clientRun = null;
|
|
819
|
-
}
|
|
826
|
+
const matchedClientRun = matchClientRun(options.prompt);
|
|
827
|
+
if (matchedClientRun) {
|
|
828
|
+
runContext = matchedClientRun.runContext;
|
|
829
|
+
toolResultsPayload = matchedClientRun.toolResultsPayload;
|
|
830
|
+
logMode = "run";
|
|
820
831
|
}
|
|
821
832
|
const storedCoord = reuseSessionKey ? getThreadCoord(reuseSessionKey) : void 0;
|
|
822
833
|
if (!runContext && storedCoord) runContext = { threadCoord: storedCoord };
|
|
@@ -883,11 +894,8 @@ function qgrid(modelId, config) {
|
|
|
883
894
|
textStarted = false;
|
|
884
895
|
}
|
|
885
896
|
const done = event.data;
|
|
886
|
-
if (
|
|
887
|
-
|
|
888
|
-
pendingToolCallIds: new Set((done.content ?? []).filter((c) => c.type === "tool-call").map((c) => c.toolCallId))
|
|
889
|
-
};
|
|
890
|
-
else clientRun = null;
|
|
897
|
+
if (matchedClientRun) clientRuns.delete(matchedClientRun.key);
|
|
898
|
+
if (done.runContext && done.finishReason === "tool-calls") rememberClientRun(done.runContext, (done.content ?? []).filter((c) => c.type === "tool-call").map((c) => c.toolCallId));
|
|
891
899
|
if (reuseSessionKey && done.runContext?.threadCoord) setThreadCoord(reuseSessionKey, done.runContext.threadCoord);
|
|
892
900
|
if (done.content) {
|
|
893
901
|
for (const item of done.content) if (item.type === "text" && !deltaTextEmitted) {
|