@gajae-code/ai 0.12.7 → 0.12.10
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/CHANGELOG.md +32 -0
- package/dist/types/auth-storage.d.ts +33 -6
- package/dist/types/index.d.ts +2 -1
- package/dist/types/model-manager.d.ts +2 -0
- package/dist/types/model-pricing.d.ts +3 -0
- package/dist/types/provider-models/special.d.ts +1 -0
- package/dist/types/providers/composer-discipline.d.ts +29 -23
- package/dist/types/providers/openai-opencodex-responses.d.ts +10 -0
- package/dist/types/providers/openai-responses-shared.d.ts +1 -0
- package/dist/types/providers/register-builtins.d.ts +2 -2
- package/dist/types/types.d.ts +15 -7
- package/dist/types/utils/fallback-transport.d.ts +23 -0
- package/dist/types/utils/oauth/anthropic.d.ts +21 -2
- package/dist/types/utils/oauth/callback-server.d.ts +7 -0
- package/dist/types/utils/oauth/types.d.ts +12 -1
- package/package.json +2 -2
- package/src/auth-gateway/server.ts +6 -0
- package/src/auth-storage.ts +325 -39
- package/src/index.ts +2 -0
- package/src/model-manager.ts +9 -3
- package/src/model-pricing.ts +68 -0
- package/src/model-thinking.ts +23 -1
- package/src/models.json +122 -24
- package/src/models.ts +7 -4
- package/src/prompts/composer-bash-policy-recovery.md +1 -0
- package/src/prompts/cursor-composer-bash-policy-recovery.md +1 -0
- package/src/prompts/cursor-composer-edit-discipline.md +7 -0
- package/src/provider-models/descriptors.ts +7 -1
- package/src/provider-models/special.ts +8 -0
- package/src/providers/composer-discipline.ts +54 -0
- package/src/providers/cursor.ts +2 -2
- package/src/providers/openai-codex/response-handler.ts +24 -2
- package/src/providers/openai-codex-responses.ts +5 -1
- package/src/providers/openai-completions.ts +109 -23
- package/src/providers/openai-opencodex-responses.ts +173 -0
- package/src/providers/openai-responses-shared.ts +14 -3
- package/src/providers/openai-responses.ts +91 -13
- package/src/providers/register-builtins.ts +4 -4
- package/src/stream.ts +61 -6
- package/src/types.ts +17 -6
- package/src/utils/discovery/openai-compatible.ts +18 -2
- package/src/utils/fallback-transport.ts +79 -6
- package/src/utils/http-inspector.ts +1 -0
- package/src/utils/idle-iterator.ts +2 -0
- package/src/utils/oauth/anthropic.ts +41 -8
- package/src/utils/oauth/callback-server.ts +64 -16
- package/src/utils/oauth/index.ts +5 -0
- package/src/utils/oauth/types.ts +13 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Api, LongContextPricing, Model, ModelCost } from "./types";
|
|
2
|
+
|
|
3
|
+
interface TieredPricing {
|
|
4
|
+
cost: ModelCost;
|
|
5
|
+
longContextPricing: LongContextPricing;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const LONG_CONTEXT_THRESHOLD = 272_000;
|
|
9
|
+
|
|
10
|
+
const GPT_5_6_SOL_PRICING: TieredPricing = {
|
|
11
|
+
cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
12
|
+
longContextPricing: {
|
|
13
|
+
threshold: LONG_CONTEXT_THRESHOLD,
|
|
14
|
+
cost: { input: 10, output: 45, cacheRead: 1, cacheWrite: 12.5 },
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// OpenAI Standard pricing: https://developers.openai.com/api/docs/pricing
|
|
19
|
+
const OPENAI_GPT_5_6_PRICING: ReadonlyMap<string, TieredPricing> = new Map([
|
|
20
|
+
["gpt-5.6", GPT_5_6_SOL_PRICING],
|
|
21
|
+
["gpt-5.6-sol", GPT_5_6_SOL_PRICING],
|
|
22
|
+
[
|
|
23
|
+
"gpt-5.6-terra",
|
|
24
|
+
{
|
|
25
|
+
cost: { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
26
|
+
longContextPricing: {
|
|
27
|
+
threshold: LONG_CONTEXT_THRESHOLD,
|
|
28
|
+
cost: { input: 4, output: 18, cacheRead: 0.4, cacheWrite: 5 },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
"gpt-5.6-luna",
|
|
34
|
+
{
|
|
35
|
+
cost: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite: 0.25 },
|
|
36
|
+
longContextPricing: {
|
|
37
|
+
threshold: LONG_CONTEXT_THRESHOLD,
|
|
38
|
+
cost: { input: 0.4, output: 1.8, cacheRead: 0.04, cacheWrite: 0.5 },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
export function getOpenAIModelCost<TApi extends Api>(model: Model<TApi>, inputTokens: number): ModelCost | undefined {
|
|
45
|
+
if (model.provider !== "openai" && model.provider !== "openai-codex") {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const pricing = OPENAI_GPT_5_6_PRICING.get(model.id);
|
|
49
|
+
if (!pricing) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return inputTokens > pricing.longContextPricing.threshold ? pricing.longContextPricing.cost : pricing.cost;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function applyOpenAIModelPricing<TApi extends Api>(model: Model<TApi>): void {
|
|
56
|
+
if (model.provider !== "openai" && model.provider !== "openai-codex") {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const pricing = OPENAI_GPT_5_6_PRICING.get(model.id);
|
|
60
|
+
if (!pricing) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
model.cost = { ...pricing.cost };
|
|
64
|
+
model.longContextPricing = {
|
|
65
|
+
threshold: pricing.longContextPricing.threshold,
|
|
66
|
+
cost: { ...pricing.longContextPricing.cost },
|
|
67
|
+
};
|
|
68
|
+
}
|
package/src/model-thinking.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CODEX_GPT_5_6_CONTEXT_CAP, isCodexGpt56Tier, isCodexProductTransport } from "./context-cap-policy";
|
|
2
|
+
import { applyOpenAIModelPricing } from "./model-pricing";
|
|
2
3
|
import { resolveOpenAICompat } from "./providers/openai-completions-compat";
|
|
3
4
|
import type { Api, Model as ApiModel, ThinkingConfig } from "./types";
|
|
4
5
|
import { isClaudeForcedToolChoiceIncapableModelId } from "./utils/tool-choice-capability";
|
|
@@ -51,6 +52,7 @@ const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effo
|
|
|
51
52
|
const GPT_5_6_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh, Effort.Max];
|
|
52
53
|
const GPT_5_5_DEFAULT_EFFORT = Effort.XHigh;
|
|
53
54
|
const KIMI_K3_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max];
|
|
55
|
+
const DEEPSEEK_V4_FLASH_0731_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High, Effort.Max];
|
|
54
56
|
|
|
55
57
|
const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High];
|
|
56
58
|
const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic";
|
|
@@ -198,7 +200,12 @@ export function refreshModelThinking<TApi extends Api>(model: ApiModel<TApi>): A
|
|
|
198
200
|
*/
|
|
199
201
|
export function applyGeneratedModelPolicies(models: ApiModel<Api>[]): void {
|
|
200
202
|
for (let index = 0; index < models.length; index++) {
|
|
201
|
-
const
|
|
203
|
+
const source = models[index]!;
|
|
204
|
+
if (source.provider === "alibaba-token-plan" && source.id === "deepseek-v4-flash-0731") {
|
|
205
|
+
source.reasoning = true;
|
|
206
|
+
source.name = "DeepSeek V4 Flash 0731";
|
|
207
|
+
}
|
|
208
|
+
const model = refreshModelThinking(source);
|
|
202
209
|
applyGeneratedModelPolicy(model);
|
|
203
210
|
models[index] = model;
|
|
204
211
|
}
|
|
@@ -377,6 +384,7 @@ function anthropicModelHasRealXHighEffort<TApi extends Api>(model: ApiModel<TApi
|
|
|
377
384
|
}
|
|
378
385
|
|
|
379
386
|
function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
|
|
387
|
+
applyOpenAIModelPricing(model);
|
|
380
388
|
const copilotLimits = model.provider === "github-copilot" ? COPILOT_GENERATED_LIMITS[model.id] : undefined;
|
|
381
389
|
if (copilotLimits) {
|
|
382
390
|
model.contextWindow = copilotLimits.contextWindow;
|
|
@@ -437,6 +445,17 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
|
|
|
437
445
|
if (model.provider === "zai" && model.id === "glm-5.2") {
|
|
438
446
|
model.contextWindow = 1_000_000;
|
|
439
447
|
}
|
|
448
|
+
if (model.provider === "alibaba-token-plan" && model.id === "deepseek-v4-flash-0731") {
|
|
449
|
+
model.contextWindow = 1_000_000;
|
|
450
|
+
model.maxTokens = 384_000;
|
|
451
|
+
model.compat = {
|
|
452
|
+
...(model.compat ?? {}),
|
|
453
|
+
supportsDeveloperRole: false,
|
|
454
|
+
supportsReasoningEffort: true,
|
|
455
|
+
reasoningContentField: "reasoning_content",
|
|
456
|
+
requiresReasoningContentForToolCalls: true,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
440
459
|
// MiniMax-M3: MiniMax exposes a 1M context tier, but usage beyond 512K is
|
|
441
460
|
// billed separately. Keep bundled/default metadata at the billing-safe 512K
|
|
442
461
|
// unless an explicit paid-tier contract is added.
|
|
@@ -617,6 +636,9 @@ function inferSupportedEfforts<TApi extends Api>(parsedModel: ParsedModel, model
|
|
|
617
636
|
if (model.provider === "kimi-code" && model.id === "k3") {
|
|
618
637
|
return KIMI_K3_EFFORTS;
|
|
619
638
|
}
|
|
639
|
+
if (model.provider === "alibaba-token-plan" && model.id === "deepseek-v4-flash-0731") {
|
|
640
|
+
return DEEPSEEK_V4_FLASH_0731_EFFORTS;
|
|
641
|
+
}
|
|
620
642
|
switch (parsedModel.family) {
|
|
621
643
|
case "openai":
|
|
622
644
|
return inferOpenAISupportedEfforts(parsedModel);
|
package/src/models.json
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"alibaba-token-plan": {
|
|
3
|
+
"deepseek-v4-flash-0731": {
|
|
4
|
+
"id": "deepseek-v4-flash-0731",
|
|
5
|
+
"name": "DeepSeek V4 Flash 0731",
|
|
6
|
+
"api": "openai-completions",
|
|
7
|
+
"provider": "alibaba-token-plan",
|
|
8
|
+
"baseUrl": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
|
|
9
|
+
"reasoning": true,
|
|
10
|
+
"input": [
|
|
11
|
+
"text"
|
|
12
|
+
],
|
|
13
|
+
"cost": {
|
|
14
|
+
"input": 0,
|
|
15
|
+
"output": 0,
|
|
16
|
+
"cacheRead": 0,
|
|
17
|
+
"cacheWrite": 0
|
|
18
|
+
},
|
|
19
|
+
"contextWindow": 1000000,
|
|
20
|
+
"maxTokens": 384000,
|
|
21
|
+
"compat": {
|
|
22
|
+
"supportsDeveloperRole": false,
|
|
23
|
+
"supportsReasoningEffort": true,
|
|
24
|
+
"reasoningContentField": "reasoning_content",
|
|
25
|
+
"requiresReasoningContentForToolCalls": true
|
|
26
|
+
},
|
|
27
|
+
"thinking": {
|
|
28
|
+
"mode": "effort",
|
|
29
|
+
"minLevel": "low",
|
|
30
|
+
"maxLevel": "max",
|
|
31
|
+
"levels": [
|
|
32
|
+
"low",
|
|
33
|
+
"high",
|
|
34
|
+
"max"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
3
38
|
"deepseek-v4-pro": {
|
|
4
39
|
"id": "deepseek-v4-pro",
|
|
5
40
|
"name": "DeepSeek V4 Pro",
|
|
@@ -58436,7 +58471,16 @@
|
|
|
58436
58471
|
"minLevel": "low",
|
|
58437
58472
|
"maxLevel": "max"
|
|
58438
58473
|
},
|
|
58439
|
-
"applyPatchToolType": "freeform"
|
|
58474
|
+
"applyPatchToolType": "freeform",
|
|
58475
|
+
"longContextPricing": {
|
|
58476
|
+
"threshold": 272000,
|
|
58477
|
+
"cost": {
|
|
58478
|
+
"input": 10,
|
|
58479
|
+
"output": 45,
|
|
58480
|
+
"cacheRead": 1,
|
|
58481
|
+
"cacheWrite": 12.5
|
|
58482
|
+
}
|
|
58483
|
+
}
|
|
58440
58484
|
},
|
|
58441
58485
|
"gpt-5.6-luna": {
|
|
58442
58486
|
"id": "gpt-5.6-luna",
|
|
@@ -58450,10 +58494,10 @@
|
|
|
58450
58494
|
"image"
|
|
58451
58495
|
],
|
|
58452
58496
|
"cost": {
|
|
58453
|
-
"input":
|
|
58454
|
-
"output":
|
|
58455
|
-
"cacheRead": 0.
|
|
58456
|
-
"cacheWrite":
|
|
58497
|
+
"input": 0.2,
|
|
58498
|
+
"output": 1.2,
|
|
58499
|
+
"cacheRead": 0.02,
|
|
58500
|
+
"cacheWrite": 0.25
|
|
58457
58501
|
},
|
|
58458
58502
|
"contextWindow": 1050000,
|
|
58459
58503
|
"maxTokens": 128000,
|
|
@@ -58462,7 +58506,16 @@
|
|
|
58462
58506
|
"minLevel": "low",
|
|
58463
58507
|
"maxLevel": "max"
|
|
58464
58508
|
},
|
|
58465
|
-
"applyPatchToolType": "freeform"
|
|
58509
|
+
"applyPatchToolType": "freeform",
|
|
58510
|
+
"longContextPricing": {
|
|
58511
|
+
"threshold": 272000,
|
|
58512
|
+
"cost": {
|
|
58513
|
+
"input": 0.4,
|
|
58514
|
+
"output": 1.8,
|
|
58515
|
+
"cacheRead": 0.04,
|
|
58516
|
+
"cacheWrite": 0.5
|
|
58517
|
+
}
|
|
58518
|
+
}
|
|
58466
58519
|
},
|
|
58467
58520
|
"gpt-5.6-sol": {
|
|
58468
58521
|
"id": "gpt-5.6-sol",
|
|
@@ -58488,7 +58541,16 @@
|
|
|
58488
58541
|
"minLevel": "low",
|
|
58489
58542
|
"maxLevel": "max"
|
|
58490
58543
|
},
|
|
58491
|
-
"applyPatchToolType": "freeform"
|
|
58544
|
+
"applyPatchToolType": "freeform",
|
|
58545
|
+
"longContextPricing": {
|
|
58546
|
+
"threshold": 272000,
|
|
58547
|
+
"cost": {
|
|
58548
|
+
"input": 10,
|
|
58549
|
+
"output": 45,
|
|
58550
|
+
"cacheRead": 1,
|
|
58551
|
+
"cacheWrite": 12.5
|
|
58552
|
+
}
|
|
58553
|
+
}
|
|
58492
58554
|
},
|
|
58493
58555
|
"gpt-5.6-terra": {
|
|
58494
58556
|
"id": "gpt-5.6-terra",
|
|
@@ -58502,10 +58564,10 @@
|
|
|
58502
58564
|
"image"
|
|
58503
58565
|
],
|
|
58504
58566
|
"cost": {
|
|
58505
|
-
"input": 2
|
|
58506
|
-
"output":
|
|
58507
|
-
"cacheRead": 0.
|
|
58508
|
-
"cacheWrite":
|
|
58567
|
+
"input": 2,
|
|
58568
|
+
"output": 12,
|
|
58569
|
+
"cacheRead": 0.2,
|
|
58570
|
+
"cacheWrite": 2.5
|
|
58509
58571
|
},
|
|
58510
58572
|
"contextWindow": 1050000,
|
|
58511
58573
|
"maxTokens": 128000,
|
|
@@ -58514,7 +58576,16 @@
|
|
|
58514
58576
|
"minLevel": "low",
|
|
58515
58577
|
"maxLevel": "max"
|
|
58516
58578
|
},
|
|
58517
|
-
"applyPatchToolType": "freeform"
|
|
58579
|
+
"applyPatchToolType": "freeform",
|
|
58580
|
+
"longContextPricing": {
|
|
58581
|
+
"threshold": 272000,
|
|
58582
|
+
"cost": {
|
|
58583
|
+
"input": 4,
|
|
58584
|
+
"output": 18,
|
|
58585
|
+
"cacheRead": 0.4,
|
|
58586
|
+
"cacheWrite": 5
|
|
58587
|
+
}
|
|
58588
|
+
}
|
|
58518
58589
|
},
|
|
58519
58590
|
"gpt-image-2": {
|
|
58520
58591
|
"id": "gpt-image-2",
|
|
@@ -59224,10 +59295,10 @@
|
|
|
59224
59295
|
"image"
|
|
59225
59296
|
],
|
|
59226
59297
|
"cost": {
|
|
59227
|
-
"input":
|
|
59228
|
-
"output":
|
|
59229
|
-
"cacheRead": 0.
|
|
59230
|
-
"cacheWrite":
|
|
59298
|
+
"input": 0.2,
|
|
59299
|
+
"output": 1.2,
|
|
59300
|
+
"cacheRead": 0.02,
|
|
59301
|
+
"cacheWrite": 0.25
|
|
59231
59302
|
},
|
|
59232
59303
|
"contextWindow": 272000,
|
|
59233
59304
|
"maxTokens": 128000,
|
|
@@ -59238,7 +59309,16 @@
|
|
|
59238
59309
|
"minLevel": "low",
|
|
59239
59310
|
"maxLevel": "max"
|
|
59240
59311
|
},
|
|
59241
|
-
"applyPatchToolType": "freeform"
|
|
59312
|
+
"applyPatchToolType": "freeform",
|
|
59313
|
+
"longContextPricing": {
|
|
59314
|
+
"threshold": 272000,
|
|
59315
|
+
"cost": {
|
|
59316
|
+
"input": 0.4,
|
|
59317
|
+
"output": 1.8,
|
|
59318
|
+
"cacheRead": 0.04,
|
|
59319
|
+
"cacheWrite": 0.5
|
|
59320
|
+
}
|
|
59321
|
+
}
|
|
59242
59322
|
},
|
|
59243
59323
|
"gpt-5.6-sol": {
|
|
59244
59324
|
"id": "gpt-5.6-sol",
|
|
@@ -59266,7 +59346,16 @@
|
|
|
59266
59346
|
"minLevel": "low",
|
|
59267
59347
|
"maxLevel": "max"
|
|
59268
59348
|
},
|
|
59269
|
-
"applyPatchToolType": "freeform"
|
|
59349
|
+
"applyPatchToolType": "freeform",
|
|
59350
|
+
"longContextPricing": {
|
|
59351
|
+
"threshold": 272000,
|
|
59352
|
+
"cost": {
|
|
59353
|
+
"input": 10,
|
|
59354
|
+
"output": 45,
|
|
59355
|
+
"cacheRead": 1,
|
|
59356
|
+
"cacheWrite": 12.5
|
|
59357
|
+
}
|
|
59358
|
+
}
|
|
59270
59359
|
},
|
|
59271
59360
|
"gpt-5.6-terra": {
|
|
59272
59361
|
"id": "gpt-5.6-terra",
|
|
@@ -59280,10 +59369,10 @@
|
|
|
59280
59369
|
"image"
|
|
59281
59370
|
],
|
|
59282
59371
|
"cost": {
|
|
59283
|
-
"input": 2
|
|
59284
|
-
"output":
|
|
59285
|
-
"cacheRead": 0.
|
|
59286
|
-
"cacheWrite":
|
|
59372
|
+
"input": 2,
|
|
59373
|
+
"output": 12,
|
|
59374
|
+
"cacheRead": 0.2,
|
|
59375
|
+
"cacheWrite": 2.5
|
|
59287
59376
|
},
|
|
59288
59377
|
"contextWindow": 272000,
|
|
59289
59378
|
"maxTokens": 128000,
|
|
@@ -59294,7 +59383,16 @@
|
|
|
59294
59383
|
"minLevel": "low",
|
|
59295
59384
|
"maxLevel": "max"
|
|
59296
59385
|
},
|
|
59297
|
-
"applyPatchToolType": "freeform"
|
|
59386
|
+
"applyPatchToolType": "freeform",
|
|
59387
|
+
"longContextPricing": {
|
|
59388
|
+
"threshold": 272000,
|
|
59389
|
+
"cost": {
|
|
59390
|
+
"input": 4,
|
|
59391
|
+
"output": 18,
|
|
59392
|
+
"cacheRead": 0.4,
|
|
59393
|
+
"cacheWrite": 5
|
|
59394
|
+
}
|
|
59395
|
+
}
|
|
59298
59396
|
},
|
|
59299
59397
|
"gpt-image-2": {
|
|
59300
59398
|
"id": "gpt-image-2",
|
|
@@ -85511,4 +85609,4 @@
|
|
|
85511
85609
|
}
|
|
85512
85610
|
}
|
|
85513
85611
|
}
|
|
85514
|
-
}
|
|
85612
|
+
}
|
package/src/models.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { getOpenAIModelCost } from "./model-pricing";
|
|
2
3
|
import { isRetiredModelKey } from "./model-retirements";
|
|
3
4
|
import { applyGeneratedModelPolicies, enrichModelThinking } from "./model-thinking";
|
|
4
5
|
// `with { type: "file" }` is embedded by `bun build --compile` and resolves to
|
|
@@ -92,10 +93,12 @@ export function getBundledModels(provider: GeneratedProvider): Model<Api>[] {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
usage.cost.
|
|
98
|
-
usage.cost.
|
|
96
|
+
const inputTokens = usage.input + usage.cacheRead + usage.cacheWrite;
|
|
97
|
+
const pricing = getOpenAIModelCost(model, inputTokens) ?? model.cost;
|
|
98
|
+
usage.cost.input = (pricing.input / 1000000) * usage.input;
|
|
99
|
+
usage.cost.output = (pricing.output / 1000000) * usage.output;
|
|
100
|
+
usage.cost.cacheRead = (pricing.cacheRead / 1000000) * usage.cacheRead;
|
|
101
|
+
usage.cost.cacheWrite = (pricing.cacheWrite / 1000000) * usage.cacheWrite;
|
|
99
102
|
usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
|
|
100
103
|
return usage.cost;
|
|
101
104
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
A Composer bash policy block interrupted a shell attempt. This is not a terminal condition: continue the same task now. Do not retry repository file I/O in bash. Use the dedicated find, search, read, and edit tools for repository work, then continue with the next safe step.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
A Composer bash policy block interrupted a shell attempt. This is not a terminal condition: continue the same task now. Do not retry repository file I/O in shell. Use Cursor-native read for files or directories, grep for search or globs, write for changes, and delete only when deletion is required; then continue with the next safe step.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
File-editing discipline for this Cursor Composer harness (this OVERRIDES contrary habits from your training):
|
|
2
|
+
|
|
3
|
+
- Inspect repository files ONLY with Cursor-native read and grep: use read for file bodies or directories, and grep for content search or glob discovery. NEVER inspect repository files through shell commands (ls, find, fd, cat, sed, awk, grep, rg, head, tail, less, more) or scripts.
|
|
4
|
+
- Modify files ONLY with Cursor-native write, or delete only when deletion is required. NEVER mutate files through shell redirection, tee, sed -i, perl -pi, inline python/node/bun scripts, or other out-of-band writes.
|
|
5
|
+
- Re-read a file after any write before relying on its contents again. Do not fabricate line anchors, paths, tool names, or tool-call arguments.
|
|
6
|
+
- Tool-call arguments must be the exact schema object requested by the native tool. Do not include Markdown, commentary, analysis text, or invented fields inside tool arguments.
|
|
7
|
+
- Use shell only for terminal operations such as tests, builds, package scripts, and git commands. A shell command string must contain only the command itself; NEVER interleave reasoning or commentary into command strings or heredocs.
|
|
@@ -48,7 +48,12 @@ import {
|
|
|
48
48
|
xiaomiModelManagerOptions,
|
|
49
49
|
zenmuxModelManagerOptions,
|
|
50
50
|
} from "./openai-compat";
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
cursorModelManagerOptions,
|
|
53
|
+
glmZcodeModelManagerOptions,
|
|
54
|
+
openCodexModelManagerOptions,
|
|
55
|
+
zaiModelManagerOptions,
|
|
56
|
+
} from "./special";
|
|
52
57
|
|
|
53
58
|
/** Catalog discovery configuration for providers that support endpoint-based model listing. */
|
|
54
59
|
export interface CatalogDiscoveryConfig {
|
|
@@ -139,6 +144,7 @@ export const PROVIDER_DESCRIPTORS: readonly ProviderDescriptor[] = [
|
|
|
139
144
|
catalog("Alibaba Token Plan", ["ALIBABA_TOKEN_PLAN_API_KEY"], { oauthProvider: "alibaba-token-plan" }),
|
|
140
145
|
),
|
|
141
146
|
descriptor("openai", "gpt-5.4", config => openaiModelManagerOptions(config)),
|
|
147
|
+
descriptor("opencodex", "gpt-5.4", () => openCodexModelManagerOptions(), { allowUnauthenticated: true }),
|
|
142
148
|
descriptor("groq", "openai/gpt-oss-120b", config => groqModelManagerOptions(config)),
|
|
143
149
|
catalogDescriptor(
|
|
144
150
|
"huggingface",
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { once } from "@gajae-code/utils";
|
|
2
2
|
import type { ModelManagerOptions } from "../model-manager";
|
|
3
|
+
import { fetchOpenCodexModels, OPENCODEX_MODEL_CACHE_TTL_MS } from "../providers/openai-opencodex-responses";
|
|
3
4
|
import { fetchCodexModels } from "../utils/discovery/codex";
|
|
5
|
+
export function openCodexModelManagerOptions(): ModelManagerOptions<"openai-responses"> {
|
|
6
|
+
return {
|
|
7
|
+
providerId: "opencodex",
|
|
8
|
+
cacheTtlMs: OPENCODEX_MODEL_CACHE_TTL_MS,
|
|
9
|
+
fetchDynamicModels: fetchOpenCodexModels,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
// ---------------------------------------------------------------------------
|
|
6
14
|
// OpenAI code provider
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import composerBashPolicyRecoveryPrompt from "../prompts/composer-bash-policy-recovery.md" with { type: "text" };
|
|
2
|
+
import cursorComposerBashPolicyRecoveryPrompt from "../prompts/cursor-composer-bash-policy-recovery.md" with {
|
|
3
|
+
type: "text",
|
|
4
|
+
};
|
|
5
|
+
import cursorComposerEditDisciplinePrompt from "../prompts/cursor-composer-edit-discipline.md" with { type: "text" };
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Anchor/edit discipline for composer-harness models (xai grok-composer-*,
|
|
3
9
|
* cursor composer-*).
|
|
@@ -30,6 +36,47 @@ export function isComposerHarnessModel(modelId: string): boolean {
|
|
|
30
36
|
return COMPOSER_MODEL_ID_PATTERN.test(modelId);
|
|
31
37
|
}
|
|
32
38
|
|
|
39
|
+
/** Stable text contract for a local shell rejection caused by Composer file-I/O discipline. */
|
|
40
|
+
export const COMPOSER_BASH_POLICY_ERROR_PREFIX = "Composer bash policy blocked repository file I/O.";
|
|
41
|
+
export const COMPOSER_BASH_POLICY_ERROR_CODE = "composer-bash-policy:repository-file-io";
|
|
42
|
+
|
|
43
|
+
export type ComposerBashPolicyToolSurface = "generic" | "cursor";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Format the model-visible policy rejection with a stable marker and the tool
|
|
47
|
+
* vocabulary the model actually receives on this provider surface.
|
|
48
|
+
*/
|
|
49
|
+
export function formatComposerBashPolicyError(surface: ComposerBashPolicyToolSurface = "generic"): string {
|
|
50
|
+
const recovery =
|
|
51
|
+
surface === "cursor"
|
|
52
|
+
? "Continue the same task with Cursor-native read, grep, write, or delete tools; do not retry repository file I/O through shell."
|
|
53
|
+
: "Continue the same task with find, search, read, and edit tools; do not retry repository file I/O through bash.";
|
|
54
|
+
return `${COMPOSER_BASH_POLICY_ERROR_PREFIX} [${COMPOSER_BASH_POLICY_ERROR_CODE}] Recovery required: ${recovery}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Matches both the structured current error and the original prefix so a
|
|
59
|
+
* resumed session can recover after an upgrade without string-version skew.
|
|
60
|
+
*/
|
|
61
|
+
export function isComposerBashPolicyBlockedError(text: string): boolean {
|
|
62
|
+
return text.includes(COMPOSER_BASH_POLICY_ERROR_PREFIX);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Matches only errors emitted directly by the current policy implementation.
|
|
67
|
+
* Live recovery must use this strict form so failed shell output that merely
|
|
68
|
+
* quotes a policy error cannot masquerade as the policy gate itself.
|
|
69
|
+
*/
|
|
70
|
+
export function isCurrentComposerBashPolicyBlockedError(text: string): boolean {
|
|
71
|
+
return text === formatComposerBashPolicyError("generic") || text === formatComposerBashPolicyError("cursor");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** One bounded, tool-enabled retry instruction for generic Composer agent loops. */
|
|
75
|
+
export const COMPOSER_BASH_POLICY_RECOVERY_PROMPT = composerBashPolicyRecoveryPrompt;
|
|
76
|
+
|
|
77
|
+
/** One bounded, tool-enabled retry instruction for Cursor's native remote tool surface. */
|
|
78
|
+
export const CURSOR_COMPOSER_BASH_POLICY_RECOVERY_PROMPT = cursorComposerBashPolicyRecoveryPrompt;
|
|
79
|
+
|
|
33
80
|
export const COMPOSER_EDIT_DISCIPLINE_PROMPT = `File-editing discipline for this Composer harness (this OVERRIDES contrary habits from your training):
|
|
34
81
|
|
|
35
82
|
- Discover file names ONLY with the find tool; search file contents ONLY with the search tool; read file bodies or line ranges ONLY with the read tool. NEVER inspect repository files through shell commands (ls, find, fd, cat, sed, awk, grep, rg, head, tail, less, more) or scripts — that output carries no hashline anchors and bypasses the agent's safety limits.
|
|
@@ -39,3 +86,10 @@ export const COMPOSER_EDIT_DISCIPLINE_PROMPT = `File-editing discipline for this
|
|
|
39
86
|
- If an edit is rejected with "anchors do not match", the rejection message prints the current lines WITH fresh anchors. Retry using exactly those printed anchors.
|
|
40
87
|
- Tool-call arguments must be the exact JSON/schema object requested by the tool. Do not include Markdown, commentary, analysis text, or invented fields inside tool arguments.
|
|
41
88
|
- Use bash only for terminal operations such as tests, builds, package scripts, and git commands. A shell command string must contain only the command itself; NEVER interleave reasoning or commentary into command strings or heredocs.`;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Cursor executes a different native tool vocabulary from the generic agent
|
|
92
|
+
* loop. Keep this prompt separate so Composer is never told to call `edit`,
|
|
93
|
+
* `find`, or `search` when those names are unavailable remotely.
|
|
94
|
+
*/
|
|
95
|
+
export const CURSOR_COMPOSER_EDIT_DISCIPLINE_PROMPT = cursorComposerEditDisciplinePrompt;
|
package/src/providers/cursor.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
|
30
30
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
31
31
|
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
32
32
|
import { flattenToolRootCombinators, toolWireSchema } from "../utils/schema";
|
|
33
|
-
import {
|
|
33
|
+
import { CURSOR_COMPOSER_EDIT_DISCIPLINE_PROMPT, isComposerHarnessModel } from "./composer-discipline";
|
|
34
34
|
import { CURSOR_CLIENT_VERSION } from "./cursor/client-version";
|
|
35
35
|
import type { McpToolDefinition } from "./cursor/gen/agent_pb";
|
|
36
36
|
import {
|
|
@@ -2329,7 +2329,7 @@ export function buildCursorSystemPromptJsons(systemPrompt: readonly string[] | u
|
|
|
2329
2329
|
// Composer-harness models need anchor/edit discipline pinned ahead of any
|
|
2330
2330
|
// host/default prompt (see composer-discipline.ts for the observed failure modes).
|
|
2331
2331
|
if (modelId !== undefined && isComposerHarnessModel(modelId)) {
|
|
2332
|
-
jsons.unshift(JSON.stringify({ role: "system", content:
|
|
2332
|
+
jsons.unshift(JSON.stringify({ role: "system", content: CURSOR_COMPOSER_EDIT_DISCIPLINE_PROMPT }));
|
|
2333
2333
|
}
|
|
2334
2334
|
return jsons;
|
|
2335
2335
|
}
|
|
@@ -19,6 +19,9 @@ export type CodexErrorInfo = {
|
|
|
19
19
|
rateLimits?: CodexRateLimits;
|
|
20
20
|
raw?: string;
|
|
21
21
|
};
|
|
22
|
+
// Matches the gate's bare rejection body ("Request blocked." / "Request
|
|
23
|
+
// blocked (…)") but never messages that merely mention blocking mid-text.
|
|
24
|
+
const REQUEST_BLOCKED_MESSAGE_RE = /^\s*request blocked\b/i;
|
|
22
25
|
|
|
23
26
|
export async function parseCodexError(response: Response): Promise<CodexErrorInfo> {
|
|
24
27
|
const raw = await response.text();
|
|
@@ -28,7 +31,7 @@ export async function parseCodexError(response: Response): Promise<CodexErrorInf
|
|
|
28
31
|
let code: string | undefined;
|
|
29
32
|
|
|
30
33
|
try {
|
|
31
|
-
const parsed = JSON.parse(raw) as { error?: Record<string, unknown
|
|
34
|
+
const parsed = JSON.parse(raw) as { error?: Record<string, unknown>; detail?: unknown };
|
|
32
35
|
const err = parsed?.error ?? {};
|
|
33
36
|
|
|
34
37
|
const headers = response.headers;
|
|
@@ -67,11 +70,30 @@ export async function parseCodexError(response: Response): Promise<CodexErrorInf
|
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
const errMessage = (err as { message?: string }).message;
|
|
70
|
-
|
|
73
|
+
// The chatgpt.com/backend-api gate rejects with a bare-`detail` body
|
|
74
|
+
// (`{"detail": "Request blocked."}`) that carries no `error.*` envelope.
|
|
75
|
+
const detail =
|
|
76
|
+
typeof parsed?.detail === "string"
|
|
77
|
+
? parsed.detail
|
|
78
|
+
: typeof (parsed?.detail as { message?: unknown } | undefined)?.message === "string"
|
|
79
|
+
? (parsed.detail as { message: string }).message
|
|
80
|
+
: undefined;
|
|
81
|
+
message = errMessage || detail || friendlyMessage || message;
|
|
71
82
|
} catch {
|
|
72
83
|
// raw body not JSON
|
|
73
84
|
}
|
|
74
85
|
|
|
86
|
+
// A bare "Request blocked" body (detail-shaped JSON or plain text) is the
|
|
87
|
+
// pre-model gate's form of the deterministic `invalid_prompt` content
|
|
88
|
+
// rejection. It never carries a structured code, so classify it explicitly
|
|
89
|
+
// here; otherwise `isInvalidPromptError`, the codex non-retryable event set,
|
|
90
|
+
// and the session-level circuit breaker all miss it and the failure surfaces
|
|
91
|
+
// as an unexplained, unrepairable "Request Blocked".
|
|
92
|
+
if (!code && REQUEST_BLOCKED_MESSAGE_RE.test(message)) {
|
|
93
|
+
code = "invalid_prompt";
|
|
94
|
+
friendlyMessage = `${message.trim().replace(/\.+$/, "")} (code=invalid_prompt)`;
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
return {
|
|
76
98
|
message,
|
|
77
99
|
status: response.status,
|
|
@@ -2823,7 +2823,7 @@ export function convertOpenAICodexResponsesTools(
|
|
|
2823
2823
|
model: Model<"openai-codex-responses">,
|
|
2824
2824
|
): CodexToolPayload[] {
|
|
2825
2825
|
const allowFreeform = supportsFreeformApplyPatchCodex(model);
|
|
2826
|
-
|
|
2826
|
+
const payloads = tools.map((tool): CodexToolPayload => {
|
|
2827
2827
|
if (allowFreeform && tool.customFormat) {
|
|
2828
2828
|
return {
|
|
2829
2829
|
type: "custom",
|
|
@@ -2847,6 +2847,10 @@ export function convertOpenAICodexResponsesTools(
|
|
|
2847
2847
|
...(effectiveStrict && { strict: true }),
|
|
2848
2848
|
};
|
|
2849
2849
|
});
|
|
2850
|
+
// Tool definitions bypass the `input`/`instructions` sanitizers, so a
|
|
2851
|
+
// leaked Harmony marker in an MCP/skill tool description or schema string
|
|
2852
|
+
// makes the gate reject every request (bare `Request blocked`).
|
|
2853
|
+
return neutralizeResponsesInputControlTokens(payloads);
|
|
2850
2854
|
}
|
|
2851
2855
|
|
|
2852
2856
|
function getString(value: unknown): string | undefined {
|