@cartanova/qgrid-cli 2.4.6 → 2.4.8

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 (73) hide show
  1. package/bundle/dist/application/qgrid/qgrid-run-lifecycle.js +15 -1
  2. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +12 -4
  3. package/bundle/dist/application/qgrid/qgrid.frame.js +20 -1
  4. package/bundle/dist/application/qgrid/qgrid.types.js +38 -2
  5. package/bundle/dist/application/request-log/request-log.model.js +49 -22
  6. package/bundle/dist/application/request-log-step/request-log-step.types.js +8 -1
  7. package/bundle/dist/application/sonamu.generated.js +37 -1
  8. package/bundle/dist/application/sonamu.generated.sso.js +25 -1
  9. package/bundle/dist/i18n/sd.generated.js +13 -1
  10. package/bundle/dist/migrations/20260718180824_alter_request_logs_add5_alter1.js +20 -0
  11. package/bundle/dist/migrations/20260718180825_alter_request_log_steps_add7_alter1.js +22 -0
  12. package/bundle/dist/utils/providers/anthropic/anthropic-constants.js +8 -2
  13. package/bundle/dist/utils/providers/anthropic/anthropic-dispatcher.js +6 -3
  14. package/bundle/dist/utils/providers/anthropic/claude-session.js +11 -7
  15. package/bundle/dist/utils/providers/anthropic/stream-json-adapter.js +117 -7
  16. package/bundle/dist/utils/providers/common/model-cost.js +45 -98
  17. package/bundle/src/application/qgrid/qgrid-run-lifecycle.test.ts +68 -0
  18. package/bundle/src/application/qgrid/qgrid-run-lifecycle.ts +14 -0
  19. package/bundle/src/application/qgrid/qgrid.dispatcher.test.ts +81 -0
  20. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +16 -9
  21. package/bundle/src/application/qgrid/qgrid.frame.ts +19 -0
  22. package/bundle/src/application/qgrid/qgrid.types.ts +36 -0
  23. package/bundle/src/application/qgrid/tool-emulation.test.ts +1 -0
  24. package/bundle/src/application/request-log/request-log.entity.json +42 -0
  25. package/bundle/src/application/request-log/request-log.model.test.ts +58 -0
  26. package/bundle/src/application/request-log/request-log.model.ts +107 -18
  27. package/bundle/src/application/request-log-step/request-log-step.entity.json +54 -0
  28. package/bundle/src/application/request-log-step/request-log-step.types.ts +7 -0
  29. package/bundle/src/application/sonamu.generated.http +26 -0
  30. package/bundle/src/application/sonamu.generated.sso.ts +24 -0
  31. package/bundle/src/application/sonamu.generated.ts +48 -0
  32. package/bundle/src/i18n/sd.generated.ts +12 -0
  33. package/bundle/src/migrations/20260718180824_alter_request_logs_add5_alter1.ts +25 -0
  34. package/bundle/src/migrations/20260718180825_alter_request_log_steps_add7_alter1.ts +29 -0
  35. package/bundle/src/utils/providers/anthropic/anthropic-constants.test.ts +15 -0
  36. package/bundle/src/utils/providers/anthropic/anthropic-constants.ts +11 -2
  37. package/bundle/src/utils/providers/anthropic/anthropic-dispatcher.ts +7 -2
  38. package/bundle/src/utils/providers/anthropic/claude-session.test.ts +26 -0
  39. package/bundle/src/utils/providers/anthropic/claude-session.ts +16 -4
  40. package/bundle/src/utils/providers/anthropic/stream-json-adapter.test.ts +131 -0
  41. package/bundle/src/utils/providers/anthropic/stream-json-adapter.ts +194 -11
  42. package/bundle/src/utils/providers/common/model-cost.test.ts +136 -2
  43. package/bundle/src/utils/providers/common/model-cost.ts +76 -99
  44. package/bundle/src/utils/providers/common/provider-dispatcher.ts +18 -0
  45. package/bundle/web-dist/client/assets/{index-BOXhNo0k.js → index-CmuPjbCl.js} +3 -3
  46. package/bundle/web-dist/client/assets/{logs-BShH_sut.js → logs-cXOsDli3.js} +1 -1
  47. package/bundle/web-dist/client/assets/{monit-CoEAp5S0.js → monit-B5Edhvy-.js} +1 -1
  48. package/bundle/web-dist/client/assets/{routes-Dr3cGnV3.js → routes-UPWScORY.js} +1 -1
  49. package/bundle/web-dist/client/assets/{sd.generated-D2Y9QeiN.js → sd.generated-DYuKzBY6.js} +1 -1
  50. package/bundle/web-dist/client/assets/{services.generated-CmxXl220.js → services.generated-D_SkWOo-.js} +1 -1
  51. package/bundle/web-dist/client/assets/show-pRp-eZIK.js +30 -0
  52. package/bundle/web-dist/client/assets/{tokens-hP1NecIg.js → tokens-DJlttxCM.js} +1 -1
  53. package/bundle/web-dist/client/assets/{use-update-token-mutation-6J1T9U2-.js → use-update-token-mutation-CpFGLWUl.js} +1 -1
  54. package/bundle/web-dist/client/index.html +2 -2
  55. package/bundle/web-dist/server/assets/{lazyRouteComponent-BlfOh9S3.js → lazyRouteComponent-Cu6IkL8A.js} +1 -1
  56. package/bundle/web-dist/server/assets/{logs-DYly4ipQ.js → logs-Dx2knN2T.js} +10 -7
  57. package/bundle/web-dist/server/assets/{monit-CYyY2S2f.js → monit-DU6PLatf.js} +2 -2
  58. package/bundle/web-dist/server/assets/{routes-Dqu3osXt.js → routes-CTco1oJZ.js} +3 -3
  59. package/bundle/web-dist/server/assets/{sd.generated-CE6D5clm.js → sd.generated-qzuRe35X.js} +12 -0
  60. package/bundle/web-dist/server/assets/{services.generated-DyZUzXiq.js → services.generated-C8q1M41a.js} +1 -1
  61. package/bundle/web-dist/server/assets/{show-BDul8xLb.js → show-B3q8_GDh.js} +47 -4
  62. package/bundle/web-dist/server/assets/{tokens-fzMW4G74.js → tokens-Bvjn15h9.js} +3 -3
  63. package/bundle/web-dist/server/assets/{use-update-token-mutation-BejUW5sw.js → use-update-token-mutation-BlZo6swF.js} +1 -1
  64. package/bundle/web-dist/server/entry-server.generated.js +8 -8
  65. package/package.json +2 -2
  66. package/skills/qgrid/SKILL.md +4 -0
  67. package/skills/qgrid/references/ai-sdk-provider-contract.md +30 -2
  68. package/skills/qgrid/references/anthropic-claude-code-runtime.md +43 -5
  69. package/skills/qgrid/references/decision-rationale.md +2 -0
  70. package/skills/qgrid/references/prompt-cache-and-usage.md +4 -1
  71. package/skills/qgrid/references/request-log-run-lifecycle.md +6 -2
  72. package/skills/qgrid/references/sonamu-api-web-flow.md +13 -0
  73. package/bundle/web-dist/client/assets/show-CXII6Iy7.js +0 -30
@@ -17,6 +17,26 @@
17
17
  },
18
18
  { "name": "step_index", "type": "integer", "desc": "ai-sdk step number (0-based)" },
19
19
  { "name": "type", "type": "enum", "id": "RequestLogStepType", "desc": "step 타입" },
20
+ {
21
+ "name": "model_name",
22
+ "type": "string",
23
+ "length": 50,
24
+ "nullable": true,
25
+ "desc": "실제 serving 모델"
26
+ },
27
+ {
28
+ "name": "requested_model_name",
29
+ "type": "string",
30
+ "length": 50,
31
+ "nullable": true,
32
+ "desc": "요청 모델 (fallback 전)"
33
+ },
34
+ {
35
+ "name": "fallback_count",
36
+ "type": "integer",
37
+ "nullable": true,
38
+ "desc": "모델 fallback 횟수"
39
+ },
20
40
  { "name": "input_tokens", "type": "integer", "nullable": true, "desc": "입력 토큰 (generate)" },
21
41
  {
22
42
  "name": "output_tokens",
@@ -31,6 +51,26 @@
31
51
  "nullable": true,
32
52
  "desc": "캐시 생성 토큰"
33
53
  },
54
+ {
55
+ "name": "cache_creation_5m_tokens",
56
+ "type": "integer",
57
+ "nullable": true,
58
+ "desc": "5분 TTL 캐시 생성 토큰"
59
+ },
60
+ {
61
+ "name": "cache_creation_1h_tokens",
62
+ "type": "integer",
63
+ "nullable": true,
64
+ "desc": "1시간 TTL 캐시 생성 토큰"
65
+ },
66
+ { "name": "cost_usd", "type": "integer", "nullable": true, "desc": "step 비용 (micro-USD)" },
67
+ {
68
+ "name": "cost_source",
69
+ "type": "string",
70
+ "length": 20,
71
+ "nullable": true,
72
+ "desc": "비용 출처(provider/pricing_table)"
73
+ },
34
74
  { "name": "duration_ms", "type": "integer", "nullable": true, "desc": "소요시간(ms)" },
35
75
  { "name": "ttft_ms", "type": "integer", "nullable": true, "desc": "Gen TTFT(ms)" },
36
76
  {
@@ -95,10 +135,17 @@
95
135
  "created_at",
96
136
  "step_index",
97
137
  "type",
138
+ "model_name",
139
+ "requested_model_name",
140
+ "fallback_count",
98
141
  "input_tokens",
99
142
  "output_tokens",
100
143
  "cache_read_tokens",
101
144
  "cache_creation_tokens",
145
+ "cache_creation_5m_tokens",
146
+ "cache_creation_1h_tokens",
147
+ "cost_usd",
148
+ "cost_source",
102
149
  "duration_ms",
103
150
  "ttft_ms",
104
151
  "finish_reason",
@@ -117,10 +164,17 @@
117
164
  "created_at",
118
165
  "step_index",
119
166
  "type",
167
+ "model_name",
168
+ "requested_model_name",
169
+ "fallback_count",
120
170
  "input_tokens",
121
171
  "output_tokens",
122
172
  "cache_read_tokens",
123
173
  "cache_creation_tokens",
174
+ "cache_creation_5m_tokens",
175
+ "cache_creation_1h_tokens",
176
+ "cost_usd",
177
+ "cost_source",
124
178
  "duration_ms",
125
179
  "ttft_ms",
126
180
  "finish_reason",
@@ -11,10 +11,17 @@ export const RequestLogStepSaveParams = RequestLogStepBaseSchema.partial({
11
11
  id: true,
12
12
  created_at: true,
13
13
  request_log_id: true,
14
+ model_name: true,
15
+ requested_model_name: true,
16
+ fallback_count: true,
14
17
  input_tokens: true,
15
18
  output_tokens: true,
16
19
  cache_read_tokens: true,
17
20
  cache_creation_tokens: true,
21
+ cache_creation_5m_tokens: true,
22
+ cache_creation_1h_tokens: true,
23
+ cost_usd: true,
24
+ cost_source: true,
18
25
  duration_ms: true,
19
26
  finish_reason: true,
20
27
  reasoning_text: true,
@@ -103,10 +103,17 @@ Content-Type: application/json
103
103
  "request_log_id": 0,
104
104
  "step_index": 0,
105
105
  "type": "generate",
106
+ "model_name": null,
107
+ "requested_model_name": null,
108
+ "fallback_count": null,
106
109
  "input_tokens": null,
107
110
  "output_tokens": null,
108
111
  "cache_read_tokens": null,
109
112
  "cache_creation_tokens": null,
113
+ "cache_creation_5m_tokens": null,
114
+ "cache_creation_1h_tokens": null,
115
+ "cost_usd": null,
116
+ "cost_source": null,
110
117
  "duration_ms": null,
111
118
  "ttft_ms": null,
112
119
  "finish_reason": null,
@@ -173,6 +180,8 @@ Content-Type: application/json
173
180
  "token_name": null,
174
181
  "project_name": null,
175
182
  "model_name": null,
183
+ "requested_model_name": null,
184
+ "fallback_count": null,
176
185
  "user_prompt": null,
177
186
  "system_prompt": null,
178
187
  "response": "RESPONSE",
@@ -180,9 +189,12 @@ Content-Type: application/json
180
189
  "output_tokens": 0,
181
190
  "cache_read_tokens": 0,
182
191
  "cache_creation_tokens": 0,
192
+ "cache_creation_5m_tokens": null,
193
+ "cache_creation_1h_tokens": null,
183
194
  "duration_ms": 0,
184
195
  "ttft_ms": 0,
185
196
  "cost_usd": null,
197
+ "cost_source": null,
186
198
  "image_cost_usd": null,
187
199
  "image_cost_method": null,
188
200
  "effort": null,
@@ -360,10 +372,17 @@ Content-Type: application/json
360
372
  "requestLogId": 0,
361
373
  "stepIndex": 0,
362
374
  "type": "generate",
375
+ "modelName": "MODELNAME",
376
+ "requestedModelName": "REQUESTEDMODELNAME",
377
+ "fallbackCount": 0,
363
378
  "inputTokens": 0,
364
379
  "outputTokens": 0,
365
380
  "cacheReadTokens": 0,
366
381
  "cacheCreationTokens": 0,
382
+ "cacheCreation5mTokens": 0,
383
+ "cacheCreation1hTokens": 0,
384
+ "costUsd": 0,
385
+ "costSource": "provider",
367
386
  "durationMs": 0,
368
387
  "ttftMs": null,
369
388
  "finishReason": "FINISHREASON",
@@ -390,10 +409,17 @@ Content-Type: application/json
390
409
  "status": "succeeded",
391
410
  "response": "RESPONSE",
392
411
  "tokenName": "TOKENNAME",
412
+ "modelName": "MODELNAME",
413
+ "requestedModelName": "REQUESTEDMODELNAME",
414
+ "fallbackCount": 0,
393
415
  "totalInputTokens": 0,
394
416
  "totalOutputTokens": 0,
395
417
  "totalCacheReadTokens": 0,
396
418
  "totalCacheCreationTokens": 0,
419
+ "totalCacheCreation5mTokens": 0,
420
+ "totalCacheCreation1hTokens": 0,
421
+ "costUsd": 0,
422
+ "costSource": "provider",
397
423
  "totalDurationMs": 0,
398
424
  "history": "HISTORY",
399
425
  "errorMessage": "ERRORMESSAGE"
@@ -23,6 +23,8 @@ export const requestLogSubsetQueries = {
23
23
  token_name: "request_logs.token_name",
24
24
  project_name: "request_logs.project_name",
25
25
  model_name: "request_logs.model_name",
26
+ requested_model_name: "request_logs.requested_model_name",
27
+ fallback_count: "request_logs.fallback_count",
26
28
  user_prompt: "request_logs.user_prompt",
27
29
  system_prompt: "request_logs.system_prompt",
28
30
  response: "request_logs.response",
@@ -30,9 +32,12 @@ export const requestLogSubsetQueries = {
30
32
  output_tokens: "request_logs.output_tokens",
31
33
  cache_read_tokens: "request_logs.cache_read_tokens",
32
34
  cache_creation_tokens: "request_logs.cache_creation_tokens",
35
+ cache_creation_5m_tokens: "request_logs.cache_creation_5m_tokens",
36
+ cache_creation_1h_tokens: "request_logs.cache_creation_1h_tokens",
33
37
  duration_ms: "request_logs.duration_ms",
34
38
  ttft_ms: "request_logs.ttft_ms",
35
39
  cost_usd: "request_logs.cost_usd",
40
+ cost_source: "request_logs.cost_source",
36
41
  image_cost_usd: "request_logs.image_cost_usd",
37
42
  image_cost_method: "request_logs.image_cost_method",
38
43
  effort: "request_logs.effort",
@@ -50,13 +55,18 @@ export const requestLogSubsetQueries = {
50
55
  token_name: "request_logs.token_name",
51
56
  project_name: "request_logs.project_name",
52
57
  model_name: "request_logs.model_name",
58
+ requested_model_name: "request_logs.requested_model_name",
59
+ fallback_count: "request_logs.fallback_count",
53
60
  input_tokens: "request_logs.input_tokens",
54
61
  output_tokens: "request_logs.output_tokens",
55
62
  cache_read_tokens: "request_logs.cache_read_tokens",
56
63
  cache_creation_tokens: "request_logs.cache_creation_tokens",
64
+ cache_creation_5m_tokens: "request_logs.cache_creation_5m_tokens",
65
+ cache_creation_1h_tokens: "request_logs.cache_creation_1h_tokens",
57
66
  duration_ms: "request_logs.duration_ms",
58
67
  ttft_ms: "request_logs.ttft_ms",
59
68
  cost_usd: "request_logs.cost_usd",
69
+ cost_source: "request_logs.cost_source",
60
70
  image_cost_usd: "request_logs.image_cost_usd",
61
71
  image_cost_method: "request_logs.image_cost_method",
62
72
  effort: "request_logs.effort",
@@ -81,10 +91,17 @@ export const requestLogStepSubsetQueries = {
81
91
  created_at: "request_log_steps.created_at",
82
92
  step_index: "request_log_steps.step_index",
83
93
  type: "request_log_steps.type",
94
+ model_name: "request_log_steps.model_name",
95
+ requested_model_name: "request_log_steps.requested_model_name",
96
+ fallback_count: "request_log_steps.fallback_count",
84
97
  input_tokens: "request_log_steps.input_tokens",
85
98
  output_tokens: "request_log_steps.output_tokens",
86
99
  cache_read_tokens: "request_log_steps.cache_read_tokens",
87
100
  cache_creation_tokens: "request_log_steps.cache_creation_tokens",
101
+ cache_creation_5m_tokens: "request_log_steps.cache_creation_5m_tokens",
102
+ cache_creation_1h_tokens: "request_log_steps.cache_creation_1h_tokens",
103
+ cost_usd: "request_log_steps.cost_usd",
104
+ cost_source: "request_log_steps.cost_source",
88
105
  duration_ms: "request_log_steps.duration_ms",
89
106
  ttft_ms: "request_log_steps.ttft_ms",
90
107
  finish_reason: "request_log_steps.finish_reason",
@@ -105,10 +122,17 @@ export const requestLogStepSubsetQueries = {
105
122
  created_at: "request_log_steps.created_at",
106
123
  step_index: "request_log_steps.step_index",
107
124
  type: "request_log_steps.type",
125
+ model_name: "request_log_steps.model_name",
126
+ requested_model_name: "request_log_steps.requested_model_name",
127
+ fallback_count: "request_log_steps.fallback_count",
108
128
  input_tokens: "request_log_steps.input_tokens",
109
129
  output_tokens: "request_log_steps.output_tokens",
110
130
  cache_read_tokens: "request_log_steps.cache_read_tokens",
111
131
  cache_creation_tokens: "request_log_steps.cache_creation_tokens",
132
+ cache_creation_5m_tokens: "request_log_steps.cache_creation_5m_tokens",
133
+ cache_creation_1h_tokens: "request_log_steps.cache_creation_1h_tokens",
134
+ cost_usd: "request_log_steps.cost_usd",
135
+ cost_source: "request_log_steps.cost_source",
112
136
  duration_ms: "request_log_steps.duration_ms",
113
137
  ttft_ms: "request_log_steps.ttft_ms",
114
138
  finish_reason: "request_log_steps.finish_reason",
@@ -92,6 +92,8 @@ export const RequestLogBaseSchema = z.object({
92
92
  token_name: z.string().max(100).nullable(),
93
93
  project_name: z.string().max(50).nullable(),
94
94
  model_name: z.string().max(50).nullable(),
95
+ requested_model_name: z.string().max(50).nullable(),
96
+ fallback_count: z.int().nullable(),
95
97
  user_prompt: z.string().nullable(),
96
98
  system_prompt: z.string().nullable(),
97
99
  response: z.string(),
@@ -99,9 +101,12 @@ export const RequestLogBaseSchema = z.object({
99
101
  output_tokens: z.int(),
100
102
  cache_read_tokens: z.int(),
101
103
  cache_creation_tokens: z.int(),
104
+ cache_creation_5m_tokens: z.int().nullable(),
105
+ cache_creation_1h_tokens: z.int().nullable(),
102
106
  duration_ms: z.int(),
103
107
  ttft_ms: z.int(),
104
108
  cost_usd: z.int().nullable(),
109
+ cost_source: z.string().max(20).nullable(),
105
110
  image_cost_usd: z.int().nullable(),
106
111
  image_cost_method: z.string().max(100).nullable(),
107
112
  effort: z.string().max(10).nullable(),
@@ -117,6 +122,8 @@ export type RequestLogBaseSchema = z.infer<typeof RequestLogBaseSchema> & {
117
122
  "token_name",
118
123
  "project_name",
119
124
  "model_name",
125
+ "requested_model_name",
126
+ "fallback_count",
120
127
  "user_prompt",
121
128
  "system_prompt",
122
129
  "response",
@@ -124,9 +131,12 @@ export type RequestLogBaseSchema = z.infer<typeof RequestLogBaseSchema> & {
124
131
  "output_tokens",
125
132
  "cache_read_tokens",
126
133
  "cache_creation_tokens",
134
+ "cache_creation_5m_tokens",
135
+ "cache_creation_1h_tokens",
127
136
  "duration_ms",
128
137
  "ttft_ms",
129
138
  "cost_usd",
139
+ "cost_source",
130
140
  "image_cost_usd",
131
141
  "image_cost_method",
132
142
  "effort",
@@ -146,10 +156,17 @@ export const RequestLogStepBaseSchema = z.object({
146
156
  request_log_id: z.int(),
147
157
  step_index: z.int(),
148
158
  type: RequestLogStepType,
159
+ model_name: z.string().max(50).nullable(),
160
+ requested_model_name: z.string().max(50).nullable(),
161
+ fallback_count: z.int().nullable(),
149
162
  input_tokens: z.int().nullable(),
150
163
  output_tokens: z.int().nullable(),
151
164
  cache_read_tokens: z.int().nullable(),
152
165
  cache_creation_tokens: z.int().nullable(),
166
+ cache_creation_5m_tokens: z.int().nullable(),
167
+ cache_creation_1h_tokens: z.int().nullable(),
168
+ cost_usd: z.int().nullable(),
169
+ cost_source: z.string().max(20).nullable(),
153
170
  duration_ms: z.int().nullable(),
154
171
  ttft_ms: z.int().nullable(),
155
172
  finish_reason: z.string().max(20).nullable(),
@@ -166,10 +183,17 @@ export const RequestLogStepBaseSchema = z.object({
166
183
  export type RequestLogStepBaseSchema = z.infer<typeof RequestLogStepBaseSchema> & {
167
184
  readonly __hasDefault__: readonly [
168
185
  "created_at",
186
+ "model_name",
187
+ "requested_model_name",
188
+ "fallback_count",
169
189
  "input_tokens",
170
190
  "output_tokens",
171
191
  "cache_read_tokens",
172
192
  "cache_creation_tokens",
193
+ "cache_creation_5m_tokens",
194
+ "cache_creation_1h_tokens",
195
+ "cost_usd",
196
+ "cost_source",
173
197
  "duration_ms",
174
198
  "ttft_ms",
175
199
  "finish_reason",
@@ -265,6 +289,8 @@ export const RequestLogSubsetA = z.object({
265
289
  token_name: z.string().max(100).nullable(),
266
290
  project_name: z.string().max(50).nullable(),
267
291
  model_name: z.string().max(50).nullable(),
292
+ requested_model_name: z.string().max(50).nullable(),
293
+ fallback_count: z.int().nullable(),
268
294
  user_prompt: z.string().nullable(),
269
295
  system_prompt: z.string().nullable(),
270
296
  response: z.string(),
@@ -272,9 +298,12 @@ export const RequestLogSubsetA = z.object({
272
298
  output_tokens: z.int(),
273
299
  cache_read_tokens: z.int(),
274
300
  cache_creation_tokens: z.int(),
301
+ cache_creation_5m_tokens: z.int().nullable(),
302
+ cache_creation_1h_tokens: z.int().nullable(),
275
303
  duration_ms: z.int(),
276
304
  ttft_ms: z.int(),
277
305
  cost_usd: z.int().nullable(),
306
+ cost_source: z.string().max(20).nullable(),
278
307
  image_cost_usd: z.int().nullable(),
279
308
  image_cost_method: z.string().max(100).nullable(),
280
309
  effort: z.string().max(10).nullable(),
@@ -291,13 +320,18 @@ export const RequestLogSubsetP = z.object({
291
320
  token_name: z.string().max(100).nullable(),
292
321
  project_name: z.string().max(50).nullable(),
293
322
  model_name: z.string().max(50).nullable(),
323
+ requested_model_name: z.string().max(50).nullable(),
324
+ fallback_count: z.int().nullable(),
294
325
  input_tokens: z.int(),
295
326
  output_tokens: z.int(),
296
327
  cache_read_tokens: z.int(),
297
328
  cache_creation_tokens: z.int(),
329
+ cache_creation_5m_tokens: z.int().nullable(),
330
+ cache_creation_1h_tokens: z.int().nullable(),
298
331
  duration_ms: z.int(),
299
332
  ttft_ms: z.int(),
300
333
  cost_usd: z.int().nullable(),
334
+ cost_source: z.string().max(20).nullable(),
301
335
  image_cost_usd: z.int().nullable(),
302
336
  image_cost_method: z.string().max(100).nullable(),
303
337
  effort: z.string().max(10).nullable(),
@@ -319,10 +353,17 @@ export const RequestLogStepSubsetA = z.object({
319
353
  created_at: z.date(),
320
354
  step_index: z.int(),
321
355
  type: RequestLogStepType,
356
+ model_name: z.string().max(50).nullable(),
357
+ requested_model_name: z.string().max(50).nullable(),
358
+ fallback_count: z.int().nullable(),
322
359
  input_tokens: z.int().nullable(),
323
360
  output_tokens: z.int().nullable(),
324
361
  cache_read_tokens: z.int().nullable(),
325
362
  cache_creation_tokens: z.int().nullable(),
363
+ cache_creation_5m_tokens: z.int().nullable(),
364
+ cache_creation_1h_tokens: z.int().nullable(),
365
+ cost_usd: z.int().nullable(),
366
+ cost_source: z.string().max(20).nullable(),
326
367
  duration_ms: z.int().nullable(),
327
368
  ttft_ms: z.int().nullable(),
328
369
  finish_reason: z.string().max(20).nullable(),
@@ -342,10 +383,17 @@ export const RequestLogStepSubsetT = z.object({
342
383
  created_at: z.date(),
343
384
  step_index: z.int(),
344
385
  type: RequestLogStepType,
386
+ model_name: z.string().max(50).nullable(),
387
+ requested_model_name: z.string().max(50).nullable(),
388
+ fallback_count: z.int().nullable(),
345
389
  input_tokens: z.int().nullable(),
346
390
  output_tokens: z.int().nullable(),
347
391
  cache_read_tokens: z.int().nullable(),
348
392
  cache_creation_tokens: z.int().nullable(),
393
+ cache_creation_5m_tokens: z.int().nullable(),
394
+ cache_creation_1h_tokens: z.int().nullable(),
395
+ cost_usd: z.int().nullable(),
396
+ cost_source: z.string().max(20).nullable(),
349
397
  duration_ms: z.int().nullable(),
350
398
  ttft_ms: z.int().nullable(),
351
399
  finish_reason: z.string().max(20).nullable(),
@@ -215,6 +215,8 @@ const entityLabels = {
215
215
  "entity.RequestLog.token_name": "토큰 이름",
216
216
  "entity.RequestLog.project_name": "프로젝트 이름",
217
217
  "entity.RequestLog.model_name": "모델 이름",
218
+ "entity.RequestLog.requested_model_name": "요청 모델 이름 (fallback 전)",
219
+ "entity.RequestLog.fallback_count": "모델 fallback 횟수",
218
220
  "entity.RequestLog.user_prompt": "사용자 프롬프트",
219
221
  "entity.RequestLog.system_prompt": "시스템 프롬프트",
220
222
  "entity.RequestLog.response": "llm 응답 전문",
@@ -222,9 +224,12 @@ const entityLabels = {
222
224
  "entity.RequestLog.output_tokens": "출력 토큰",
223
225
  "entity.RequestLog.cache_read_tokens": "캐시 읽기 토큰",
224
226
  "entity.RequestLog.cache_creation_tokens": "캐시 생성 토큰",
227
+ "entity.RequestLog.cache_creation_5m_tokens": "5분 TTL 캐시 생성 토큰",
228
+ "entity.RequestLog.cache_creation_1h_tokens": "1시간 TTL 캐시 생성 토큰",
225
229
  "entity.RequestLog.duration_ms": "응답 소요시간(ms)",
226
230
  "entity.RequestLog.ttft_ms": "first gen TTFT(ms)",
227
231
  "entity.RequestLog.cost_usd": "API 비용",
232
+ "entity.RequestLog.cost_source": "비용 출처(provider/pricing_table/mixed)",
228
233
  "entity.RequestLog.image_cost_usd": "이미지 생성 추정 비용",
229
234
  "entity.RequestLog.image_cost_method": "이미지 비용 추정 방식",
230
235
  "entity.RequestLog.effort": "effort level",
@@ -247,10 +252,17 @@ const entityLabels = {
247
252
  "entity.RequestLogStep.request_log": "부모 request log ID",
248
253
  "entity.RequestLogStep.step_index": "ai-sdk step number (0-based)",
249
254
  "entity.RequestLogStep.type": "step 타입",
255
+ "entity.RequestLogStep.model_name": "실제 serving 모델",
256
+ "entity.RequestLogStep.requested_model_name": "요청 모델 (fallback 전)",
257
+ "entity.RequestLogStep.fallback_count": "모델 fallback 횟수",
250
258
  "entity.RequestLogStep.input_tokens": "입력 토큰 (generate)",
251
259
  "entity.RequestLogStep.output_tokens": "출력 토큰 (generate)",
252
260
  "entity.RequestLogStep.cache_read_tokens": "캐시 읽기 토큰",
253
261
  "entity.RequestLogStep.cache_creation_tokens": "캐시 생성 토큰",
262
+ "entity.RequestLogStep.cache_creation_5m_tokens": "5분 TTL 캐시 생성 토큰",
263
+ "entity.RequestLogStep.cache_creation_1h_tokens": "1시간 TTL 캐시 생성 토큰",
264
+ "entity.RequestLogStep.cost_usd": "step 비용 (micro-USD)",
265
+ "entity.RequestLogStep.cost_source": "비용 출처(provider/pricing_table)",
254
266
  "entity.RequestLogStep.duration_ms": "소요시간(ms)",
255
267
  "entity.RequestLogStep.ttft_ms": "Gen TTFT(ms)",
256
268
  "entity.RequestLogStep.finish_reason": "완료 사유",
@@ -0,0 +1,25 @@
1
+ import { type Knex } from "knex";
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex.schema.alterTable("request_logs", (table) => {
5
+ // add
6
+ table.integer("cache_creation_1h_tokens").nullable();
7
+ table.integer("cache_creation_5m_tokens").nullable();
8
+ table.string("cost_source", 20).nullable();
9
+ table.integer("fallback_count").nullable();
10
+ table.string("requested_model_name", 50).nullable();
11
+ });
12
+ }
13
+
14
+ export async function down(knex: Knex): Promise<void> {
15
+ await knex.schema.alterTable("request_logs", (table) => {
16
+ // rollback - add
17
+ table.dropColumns(
18
+ "cache_creation_1h_tokens",
19
+ "cache_creation_5m_tokens",
20
+ "cost_source",
21
+ "fallback_count",
22
+ "requested_model_name",
23
+ );
24
+ });
25
+ }
@@ -0,0 +1,29 @@
1
+ import { type Knex } from "knex";
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex.schema.alterTable("request_log_steps", (table) => {
5
+ // add
6
+ table.integer("cache_creation_1h_tokens").nullable();
7
+ table.integer("cache_creation_5m_tokens").nullable();
8
+ table.string("cost_source", 20).nullable();
9
+ table.integer("cost_usd").nullable();
10
+ table.integer("fallback_count").nullable();
11
+ table.string("model_name", 50).nullable();
12
+ table.string("requested_model_name", 50).nullable();
13
+ });
14
+ }
15
+
16
+ export async function down(knex: Knex): Promise<void> {
17
+ await knex.schema.alterTable("request_log_steps", (table) => {
18
+ // rollback - add
19
+ table.dropColumns(
20
+ "cache_creation_1h_tokens",
21
+ "cache_creation_5m_tokens",
22
+ "cost_source",
23
+ "cost_usd",
24
+ "fallback_count",
25
+ "model_name",
26
+ "requested_model_name",
27
+ );
28
+ });
29
+ }
@@ -6,6 +6,7 @@ import {
6
6
  canonicalAnthropicModel,
7
7
  hasOneMillionSuffix,
8
8
  needsCli1mSuffix,
9
+ requiresAdaptiveThinking,
9
10
  supports1MContext,
10
11
  } from "./anthropic-constants";
11
12
 
@@ -16,6 +17,7 @@ describe("canonicalAnthropicModel", () => {
16
17
 
17
18
  it("provider prefix 를 제거한다", () => {
18
19
  expect(canonicalAnthropicModel("anthropic/claude-opus-4-8")).toBe("claude-opus-4-8");
20
+ expect(canonicalAnthropicModel("anthropic/claude-fable-5")).toBe("claude-fable-5");
19
21
  });
20
22
 
21
23
  it("[1m] suffix 를 base canonical 에서 제거한다", () => {
@@ -26,6 +28,8 @@ describe("canonicalAnthropicModel", () => {
26
28
 
27
29
  describe("Anthropic 1M context policy", () => {
28
30
  it("지원 모델은 실측 확인된 exact set 만 true", () => {
31
+ expect(supports1MContext("claude-fable-5")).toBe(true);
32
+ expect(supports1MContext("claude-sonnet-5")).toBe(true);
29
33
  expect(supports1MContext("claude-sonnet-4-6")).toBe(true);
30
34
  expect(supports1MContext("claude-opus-4-6")).toBe(true);
31
35
  expect(supports1MContext("claude-opus-4-8")).toBe(true);
@@ -37,6 +41,8 @@ describe("Anthropic 1M context policy", () => {
37
41
  });
38
42
 
39
43
  it("CLI suffix 필요 모델과 기본 1M 모델을 분리한다", () => {
44
+ expect(needsCli1mSuffix("claude-fable-5")).toBe(false);
45
+ expect(needsCli1mSuffix("claude-sonnet-5")).toBe(false);
40
46
  expect(needsCli1mSuffix("claude-sonnet-4-6")).toBe(true);
41
47
  expect(needsCli1mSuffix("claude-opus-4-6")).toBe(true);
42
48
  expect(needsCli1mSuffix("claude-opus-4-8")).toBe(false);
@@ -58,3 +64,12 @@ describe("Anthropic 1M context policy", () => {
58
64
  expect(() => assertSupportedOneMillionSuffix("anthropic/claude-sonnet-4-6[1m]")).not.toThrow();
59
65
  });
60
66
  });
67
+
68
+ describe("Anthropic thinking policy", () => {
69
+ it("Fable 5 만 always-on adaptive thinking 으로 실행한다", () => {
70
+ expect(requiresAdaptiveThinking("claude-fable-5")).toBe(true);
71
+ expect(requiresAdaptiveThinking("anthropic/claude-fable-5")).toBe(true);
72
+ expect(requiresAdaptiveThinking("claude-sonnet-5")).toBe(false);
73
+ expect(requiresAdaptiveThinking("claude-opus-4-8")).toBe(false);
74
+ });
75
+ });
@@ -42,15 +42,18 @@ export function hasOneMillionSuffix(model?: string): boolean {
42
42
  return model !== undefined && ONE_MILLION_SUFFIX_RE.test(model);
43
43
  }
44
44
 
45
- // 1M 지원 여부는 Claude Code 유출본의 prefix 판정이 아니라 qgrid 실측 기준 exact set 으로 고정한다.
46
- // opus-4-7 은 실측 전까지 미포함. 새 모델 추가 시 실제 claude CLI contextWindow 동작으로 확인한다.
45
+ // 1M 지원 여부는 Claude Code 내부의 광범위한 prefix 판정이 아니라 qgrid에서 확인한 exact set 으로
46
+ // 고정한다. opus-4-7 은 실측 전까지 미포함. 새 모델 추가 시 공식 사양과 실제 CLI 동작을 확인한다.
47
47
  const ONE_MILLION_CONTEXT_MODELS = new Set([
48
+ "claude-fable-5",
49
+ "claude-sonnet-5",
48
50
  "claude-sonnet-4-6",
49
51
  "claude-opus-4-6",
50
52
  "claude-opus-4-8",
51
53
  ]);
52
54
 
53
55
  const CLI_ONE_MILLION_SUFFIX_MODELS = new Set(["claude-sonnet-4-6", "claude-opus-4-6"]);
56
+ const ALWAYS_ADAPTIVE_THINKING_MODELS = new Set(["claude-fable-5"]);
54
57
 
55
58
  // 불변 계약: suffix 가 필요한 모델은 반드시 1M 지원 모델의 부분집합이어야 한다. 역방향 불일치(suffix
56
59
  // 대상인데 지원 집합엔 없음)면 needsCli1mSuffix=true·supports1MContext=false 가 동시에 나서
@@ -70,6 +73,12 @@ export function needsCli1mSuffix(model?: string): boolean {
70
73
  return CLI_ONE_MILLION_SUFFIX_MODELS.has(canonicalAnthropicModel(model));
71
74
  }
72
75
 
76
+ // Fable 5 는 adaptive thinking 이 유일한 thinking mode 이므로 disabled 로 실행할 수 없다.
77
+ // 나머지 모델은 qgrid 의 기존 저비용/비추론 경로를 그대로 유지한다.
78
+ export function requiresAdaptiveThinking(model?: string): boolean {
79
+ return ALWAYS_ADAPTIVE_THINKING_MODELS.has(canonicalAnthropicModel(model));
80
+ }
81
+
73
82
  export function assertSupportedOneMillionSuffix(model?: string): void {
74
83
  if (!hasOneMillionSuffix(model)) return;
75
84
  const canonical = canonicalAnthropicModel(model);
@@ -347,7 +347,8 @@ export class AnthropicDispatcher implements ProviderDispatcher {
347
347
  ? `${session.text.slice(0, 500)}…(${session.text.length} chars)`
348
348
  : session.text
349
349
  : `empty text, outputTokens=${session.usage.outputTokens}`;
350
- throw new Error(`claude error (${token.name}) [${reason}]: ${body}`);
350
+ const refusal = session.refusal ? ` refusal=${JSON.stringify(session.refusal)}` : "";
351
+ throw new Error(`claude error (${token.name}) [${reason}]: ${body}${refusal}`);
351
352
  }
352
353
 
353
354
  return {
@@ -357,7 +358,11 @@ export class AnthropicDispatcher implements ProviderDispatcher {
357
358
  durationMs: session.durationMs,
358
359
  ttftMs: session.ttftMs,
359
360
  costUsd: session.costUsd,
360
- model,
361
+ // Claude Code 는 Fable refusal 을 Opus 로 자동 fallback 할 수 있다. 요청 모델을
362
+ // 그대로 serving model 로 기록하면 비용/감사 로그가 모두 틀어지므로 둘을 구분한다.
363
+ model: session.servedModel ?? model,
364
+ requestedModel: model,
365
+ modelFallbacks: session.modelFallbacks,
361
366
  // systemHash 는 상위 issueConvContext 가 채운다(여기선 비운다).
362
367
  threadCoord: {
363
368
  workerId: makeAnthropicWorkerId(token.id),
@@ -12,6 +12,7 @@ import {
12
12
  oneMillionEnv,
13
13
  runClaudeSession,
14
14
  structuredOutputRetriesEnv,
15
+ thinkingEnv,
15
16
  SYSTEM_PROMPT_ARGV_MAX_BYTES,
16
17
  withSessionLock,
17
18
  } from "./claude-session";
@@ -132,6 +133,15 @@ describe("buildClaudeArgs (멀티턴/격리/structured)", () => {
132
133
  expect(oneMillionEnv(false)).toEqual({ CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" });
133
134
  });
134
135
 
136
+ it("thinkingEnv: Fable 은 always-on adaptive thinking 을 보존하고 나머지는 비활성화", () => {
137
+ expect(thinkingEnv("claude-fable-5")).toEqual({});
138
+ expect(thinkingEnv("anthropic/claude-fable-5")).toEqual({});
139
+ expect(thinkingEnv("claude-opus-4-8")).toEqual({
140
+ CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING: "1",
141
+ MAX_THINKING_TOKENS: "0",
142
+ });
143
+ });
144
+
135
145
  // SON-495: structured output retry 를 1 로 고정한다.
136
146
  it("structuredOutputRetriesEnv: 미설정/비정상 값은 1 로 기본 고정", () => {
137
147
  expect(structuredOutputRetriesEnv(undefined)).toEqual({ MAX_STRUCTURED_OUTPUT_RETRIES: "1" });
@@ -221,6 +231,22 @@ describe("buildClaudeArgs (멀티턴/격리/structured)", () => {
221
231
  expect(args[args.indexOf("--model") + 1]).toBe("claude-opus-4-8");
222
232
  });
223
233
 
234
+ it("Fable 5 는 항상 켜진 adaptive thinking 을 끄지 않고 effort 만 전달한다", () => {
235
+ const args = buildClaudeArgs({
236
+ model: "claude-fable-5",
237
+ sessionId: "u",
238
+ effort: "low",
239
+ });
240
+ expect(args[args.indexOf("--model") + 1]).toBe("claude-fable-5");
241
+ expect(args).not.toContain("--thinking");
242
+ expect(args[args.indexOf("--effort") + 1]).toBe("low");
243
+ });
244
+
245
+ it("기존 Anthropic 모델은 thinking disabled 계약을 유지한다", () => {
246
+ const args = buildClaudeArgs({ model: "claude-opus-4-8", sessionId: "u" });
247
+ expect(args[args.indexOf("--thinking") + 1]).toBe("disabled");
248
+ });
249
+
224
250
  it("non-structured(jsonSchema 없음): --allowed-tools 미부여 (P2-7)", () => {
225
251
  const args = buildClaudeArgs({ model: "m", sessionId: "u" });
226
252
  expect(args).not.toContain("--allowed-tools");