@gajae-code/ai 0.18.1 → 0.18.5
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 +26 -0
- package/dist/types/index.d.ts +0 -1
- package/package.json +3 -3
- package/src/index.ts +0 -1
- package/src/model-pricing.ts +10 -0
- package/src/models.json +210 -0
- package/src/providers/anthropic.ts +25 -1
- package/src/providers/kiro-codewhisperer.ts +55 -25
- package/src/providers/openai-completions.ts +19 -1
- package/dist/types/utils/h2-fetch.d.ts +0 -35
- package/src/utils/h2-fetch.ts +0 -133
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.18.5] - 2026-09-30
|
|
6
|
+
|
|
7
|
+
## [0.18.4] - 2026-09-30
|
|
8
|
+
|
|
9
|
+
## [0.18.3] - 2026-09-30
|
|
10
|
+
|
|
11
|
+
## [0.18.2] - 2026-09-30
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Bundled Claude Sonnet 5.5: `anthropic/claude-sonnet-5-5` and Amazon Bedrock `anthropic.claude-sonnet-5-5` (plus `au.`/`eu.`/`global.`/`jp.`/`us.` inference profiles), with 1M context, 128K output, and $2/$10 per MTok pricing (#6111).
|
|
16
|
+
|
|
17
|
+
- Bundled GPT-6.1 Sol for the Codex provider: `openai-codex/gpt-6.1-sol` (272K context, 128K output, reasoning effort low..max) with $2/$10 per MTok and $0.10 cached-input pricing (#6147). Presets and model-profile defaults are unchanged.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- Removed `installH2Fetch`. Bun's HTTP/2 client can wedge a pooled connection while uploading a large request body (~300 KB, a typical long agent context): every later request on that connection waits indefinitely for response headers. All `fetch()` traffic now uses HTTP/1.1.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- A large (≥1 MB) request to a custom Anthropic-compatible endpoint is now retried when its connection drops before any response (ECONNRESET, socket closed, `Connection error`). Previously the one-upload ceiling meant for first-event stalls also covered these failures, so a single network blip ended the turn. Server responses (e.g. 529) and first-event timeouts still respect the ceiling (#6072).
|
|
26
|
+
|
|
27
|
+
- Kiro (OAuth CodeWhisperer transport) now replays earlier tool calls as structured `toolUses` and sends each tool result as `{ toolUseId, status, content: [{ text }] }`, linked to the call it answers, instead of serializing calls into assistant text and nesting results. Parallel tool results share one history entry, and image results are marked `[image omitted]` rather than sent empty (#6079).
|
|
28
|
+
|
|
29
|
+
- OpenAI-compatible completions requests now log OpenAI SDK connection timeouts and retries to the gjc log. Previously a request stalled before response headers retried silently for up to several minutes.
|
|
30
|
+
|
|
5
31
|
## [0.18.1] - 2026-09-29
|
|
6
32
|
|
|
7
33
|
### Changed
|
package/dist/types/index.d.ts
CHANGED
|
@@ -46,7 +46,6 @@ export * from "./utils/anthropic-auth";
|
|
|
46
46
|
export * from "./utils/discovery";
|
|
47
47
|
export * from "./utils/event-stream";
|
|
48
48
|
export * from "./utils/fallback-transport";
|
|
49
|
-
export * from "./utils/h2-fetch";
|
|
50
49
|
export * from "./utils/oauth";
|
|
51
50
|
export type { OAuthCredentials, OAuthLoginOptions, OAuthProvider, OAuthProviderId, OAuthProviderInfo, } from "./utils/oauth/types";
|
|
52
51
|
export * from "./utils/overflow";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/ai",
|
|
4
|
-
"version": "0.18.
|
|
4
|
+
"version": "0.18.5",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
42
42
|
"@anthropic-ai/sdk": "^0.94.0",
|
|
43
43
|
"@bufbuild/protobuf": "^2.12.0",
|
|
44
|
-
"@gajae-code/natives": "0.18.
|
|
45
|
-
"@gajae-code/utils": "0.18.
|
|
44
|
+
"@gajae-code/natives": "0.18.5",
|
|
45
|
+
"@gajae-code/utils": "0.18.5",
|
|
46
46
|
"openai": "^6.36.0",
|
|
47
47
|
"partial-json": "^0.1.7",
|
|
48
48
|
"zod": "4.4.3"
|
package/src/index.ts
CHANGED
|
@@ -46,7 +46,6 @@ export * from "./utils/anthropic-auth";
|
|
|
46
46
|
export * from "./utils/discovery";
|
|
47
47
|
export * from "./utils/event-stream";
|
|
48
48
|
export * from "./utils/fallback-transport";
|
|
49
|
-
export * from "./utils/h2-fetch";
|
|
50
49
|
export * from "./utils/oauth";
|
|
51
50
|
export type {
|
|
52
51
|
OAuthCredentials,
|
package/src/model-pricing.ts
CHANGED
|
@@ -35,6 +35,15 @@ const GPT_6_SOL_PRICING: TieredPricing = {
|
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
// TODO(gpt-6.1-sol): cache-write and long-context prices not yet published; derived from gpt-6-sol / 2x-1.5x rule
|
|
39
|
+
const GPT_6_1_SOL_PRICING: TieredPricing = {
|
|
40
|
+
cost: { input: 2, output: 10, cacheRead: 0.1, cacheWrite: 2.5 },
|
|
41
|
+
longContextPricing: {
|
|
42
|
+
threshold: LONG_CONTEXT_THRESHOLD,
|
|
43
|
+
cost: { input: 4, output: 15, cacheRead: 0.2, cacheWrite: 5 },
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
38
47
|
// GPT-6 Luna: $0.10/$0.50 standard, cache read $0.01, cache write $0.125;
|
|
39
48
|
// inputs past 272K apply 2x to input/cache and 1.5x to output.
|
|
40
49
|
const GPT_6_LUNA_PRICING: TieredPricing = {
|
|
@@ -49,6 +58,7 @@ const GPT_6_LUNA_PRICING: TieredPricing = {
|
|
|
49
58
|
const OPENAI_GPT_5_6_PRICING: ReadonlyMap<string, TieredPricing> = new Map([
|
|
50
59
|
["gpt-6-astra", GPT_6_ASTRA_PRICING],
|
|
51
60
|
["gpt-6-sol", GPT_6_SOL_PRICING],
|
|
61
|
+
["gpt-6.1-sol", GPT_6_1_SOL_PRICING],
|
|
52
62
|
["gpt-6-luna", GPT_6_LUNA_PRICING],
|
|
53
63
|
["gpt-5.6", GPT_5_6_SOL_PRICING],
|
|
54
64
|
["gpt-5.6-sol", GPT_5_6_SOL_PRICING],
|
package/src/models.json
CHANGED
|
@@ -964,6 +964,31 @@
|
|
|
964
964
|
"maxLevel": "high"
|
|
965
965
|
}
|
|
966
966
|
},
|
|
967
|
+
"anthropic.claude-sonnet-5-5": {
|
|
968
|
+
"id": "anthropic.claude-sonnet-5-5",
|
|
969
|
+
"name": "Anthropic Sonnet 5.5",
|
|
970
|
+
"api": "bedrock-converse-stream",
|
|
971
|
+
"provider": "amazon-bedrock",
|
|
972
|
+
"baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
973
|
+
"reasoning": true,
|
|
974
|
+
"input": [
|
|
975
|
+
"text",
|
|
976
|
+
"image"
|
|
977
|
+
],
|
|
978
|
+
"cost": {
|
|
979
|
+
"input": 2,
|
|
980
|
+
"output": 10,
|
|
981
|
+
"cacheRead": 0.2,
|
|
982
|
+
"cacheWrite": 2.5
|
|
983
|
+
},
|
|
984
|
+
"contextWindow": 1000000,
|
|
985
|
+
"maxTokens": 128000,
|
|
986
|
+
"thinking": {
|
|
987
|
+
"mode": "anthropic-budget-effort",
|
|
988
|
+
"minLevel": "minimal",
|
|
989
|
+
"maxLevel": "high"
|
|
990
|
+
}
|
|
991
|
+
},
|
|
967
992
|
"apac.amazon.nova-lite-v1:0": {
|
|
968
993
|
"id": "apac.amazon.nova-lite-v1:0",
|
|
969
994
|
"name": "Nova Lite (APAC)",
|
|
@@ -1308,6 +1333,31 @@
|
|
|
1308
1333
|
"maxLevel": "high"
|
|
1309
1334
|
}
|
|
1310
1335
|
},
|
|
1336
|
+
"au.anthropic.claude-sonnet-5-5": {
|
|
1337
|
+
"id": "au.anthropic.claude-sonnet-5-5",
|
|
1338
|
+
"name": "Anthropic Sonnet 5.5 (AU)",
|
|
1339
|
+
"api": "bedrock-converse-stream",
|
|
1340
|
+
"provider": "amazon-bedrock",
|
|
1341
|
+
"baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
1342
|
+
"reasoning": true,
|
|
1343
|
+
"input": [
|
|
1344
|
+
"text",
|
|
1345
|
+
"image"
|
|
1346
|
+
],
|
|
1347
|
+
"cost": {
|
|
1348
|
+
"input": 2.2,
|
|
1349
|
+
"output": 11,
|
|
1350
|
+
"cacheRead": 0.22,
|
|
1351
|
+
"cacheWrite": 2.75
|
|
1352
|
+
},
|
|
1353
|
+
"contextWindow": 1000000,
|
|
1354
|
+
"maxTokens": 128000,
|
|
1355
|
+
"thinking": {
|
|
1356
|
+
"mode": "anthropic-budget-effort",
|
|
1357
|
+
"minLevel": "minimal",
|
|
1358
|
+
"maxLevel": "high"
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1311
1361
|
"ca.amazon.nova-lite-v1:0": {
|
|
1312
1362
|
"id": "ca.amazon.nova-lite-v1:0",
|
|
1313
1363
|
"name": "Nova Lite (CA)",
|
|
@@ -2072,6 +2122,31 @@
|
|
|
2072
2122
|
"maxLevel": "high"
|
|
2073
2123
|
}
|
|
2074
2124
|
},
|
|
2125
|
+
"eu.anthropic.claude-sonnet-5-5": {
|
|
2126
|
+
"id": "eu.anthropic.claude-sonnet-5-5",
|
|
2127
|
+
"name": "Anthropic Sonnet 5.5 (EU)",
|
|
2128
|
+
"api": "bedrock-converse-stream",
|
|
2129
|
+
"provider": "amazon-bedrock",
|
|
2130
|
+
"baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
2131
|
+
"reasoning": true,
|
|
2132
|
+
"input": [
|
|
2133
|
+
"text",
|
|
2134
|
+
"image"
|
|
2135
|
+
],
|
|
2136
|
+
"cost": {
|
|
2137
|
+
"input": 2.2,
|
|
2138
|
+
"output": 11,
|
|
2139
|
+
"cacheRead": 0.22,
|
|
2140
|
+
"cacheWrite": 2.75
|
|
2141
|
+
},
|
|
2142
|
+
"contextWindow": 1000000,
|
|
2143
|
+
"maxTokens": 128000,
|
|
2144
|
+
"thinking": {
|
|
2145
|
+
"mode": "anthropic-budget-effort",
|
|
2146
|
+
"minLevel": "minimal",
|
|
2147
|
+
"maxLevel": "high"
|
|
2148
|
+
}
|
|
2149
|
+
},
|
|
2075
2150
|
"eu.mistral.pixtral-large-2502-v1:0": {
|
|
2076
2151
|
"id": "eu.mistral.pixtral-large-2502-v1:0",
|
|
2077
2152
|
"name": "Pixtral Large (25.02) (EU)",
|
|
@@ -2477,6 +2552,31 @@
|
|
|
2477
2552
|
"maxLevel": "high"
|
|
2478
2553
|
}
|
|
2479
2554
|
},
|
|
2555
|
+
"global.anthropic.claude-sonnet-5-5": {
|
|
2556
|
+
"id": "global.anthropic.claude-sonnet-5-5",
|
|
2557
|
+
"name": "Anthropic Sonnet 5.5 (Global)",
|
|
2558
|
+
"api": "bedrock-converse-stream",
|
|
2559
|
+
"provider": "amazon-bedrock",
|
|
2560
|
+
"baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
2561
|
+
"reasoning": true,
|
|
2562
|
+
"input": [
|
|
2563
|
+
"text",
|
|
2564
|
+
"image"
|
|
2565
|
+
],
|
|
2566
|
+
"cost": {
|
|
2567
|
+
"input": 2,
|
|
2568
|
+
"output": 10,
|
|
2569
|
+
"cacheRead": 0.2,
|
|
2570
|
+
"cacheWrite": 2.5
|
|
2571
|
+
},
|
|
2572
|
+
"contextWindow": 1000000,
|
|
2573
|
+
"maxTokens": 128000,
|
|
2574
|
+
"thinking": {
|
|
2575
|
+
"mode": "anthropic-budget-effort",
|
|
2576
|
+
"minLevel": "minimal",
|
|
2577
|
+
"maxLevel": "high"
|
|
2578
|
+
}
|
|
2579
|
+
},
|
|
2480
2580
|
"global.openai.gpt-5.6-luna": {
|
|
2481
2581
|
"id": "global.openai.gpt-5.6-luna",
|
|
2482
2582
|
"name": "GPT-5.6 Luna (Global)",
|
|
@@ -3020,6 +3120,31 @@
|
|
|
3020
3120
|
"maxLevel": "high"
|
|
3021
3121
|
}
|
|
3022
3122
|
},
|
|
3123
|
+
"jp.anthropic.claude-sonnet-5-5": {
|
|
3124
|
+
"id": "jp.anthropic.claude-sonnet-5-5",
|
|
3125
|
+
"name": "Anthropic Sonnet 5.5 (JP)",
|
|
3126
|
+
"api": "bedrock-converse-stream",
|
|
3127
|
+
"provider": "amazon-bedrock",
|
|
3128
|
+
"baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
3129
|
+
"reasoning": true,
|
|
3130
|
+
"input": [
|
|
3131
|
+
"text",
|
|
3132
|
+
"image"
|
|
3133
|
+
],
|
|
3134
|
+
"cost": {
|
|
3135
|
+
"input": 2.2,
|
|
3136
|
+
"output": 11,
|
|
3137
|
+
"cacheRead": 0.22,
|
|
3138
|
+
"cacheWrite": 2.75
|
|
3139
|
+
},
|
|
3140
|
+
"contextWindow": 1000000,
|
|
3141
|
+
"maxTokens": 128000,
|
|
3142
|
+
"thinking": {
|
|
3143
|
+
"mode": "anthropic-budget-effort",
|
|
3144
|
+
"minLevel": "minimal",
|
|
3145
|
+
"maxLevel": "high"
|
|
3146
|
+
}
|
|
3147
|
+
},
|
|
3023
3148
|
"meta.llama3-1-405b-instruct-v1:0": {
|
|
3024
3149
|
"id": "meta.llama3-1-405b-instruct-v1:0",
|
|
3025
3150
|
"name": "Llama 3.1 405B Instruct",
|
|
@@ -4501,6 +4626,31 @@
|
|
|
4501
4626
|
"maxLevel": "high"
|
|
4502
4627
|
}
|
|
4503
4628
|
},
|
|
4629
|
+
"us.anthropic.claude-sonnet-5-5": {
|
|
4630
|
+
"id": "us.anthropic.claude-sonnet-5-5",
|
|
4631
|
+
"name": "Anthropic Sonnet 5.5 (US)",
|
|
4632
|
+
"api": "bedrock-converse-stream",
|
|
4633
|
+
"provider": "amazon-bedrock",
|
|
4634
|
+
"baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
|
|
4635
|
+
"reasoning": true,
|
|
4636
|
+
"input": [
|
|
4637
|
+
"text",
|
|
4638
|
+
"image"
|
|
4639
|
+
],
|
|
4640
|
+
"cost": {
|
|
4641
|
+
"input": 2.2,
|
|
4642
|
+
"output": 11,
|
|
4643
|
+
"cacheRead": 0.22,
|
|
4644
|
+
"cacheWrite": 2.75
|
|
4645
|
+
},
|
|
4646
|
+
"contextWindow": 1000000,
|
|
4647
|
+
"maxTokens": 128000,
|
|
4648
|
+
"thinking": {
|
|
4649
|
+
"mode": "anthropic-budget-effort",
|
|
4650
|
+
"minLevel": "minimal",
|
|
4651
|
+
"maxLevel": "high"
|
|
4652
|
+
}
|
|
4653
|
+
},
|
|
4504
4654
|
"us.deepseek.r1-v1:0": {
|
|
4505
4655
|
"id": "us.deepseek.r1-v1:0",
|
|
4506
4656
|
"name": "DeepSeek-R1 (US)",
|
|
@@ -5656,6 +5806,31 @@
|
|
|
5656
5806
|
"minLevel": "minimal",
|
|
5657
5807
|
"maxLevel": "max"
|
|
5658
5808
|
}
|
|
5809
|
+
},
|
|
5810
|
+
"claude-sonnet-5-5": {
|
|
5811
|
+
"id": "claude-sonnet-5-5",
|
|
5812
|
+
"name": "Anthropic Sonnet 5.5",
|
|
5813
|
+
"api": "anthropic-messages",
|
|
5814
|
+
"provider": "anthropic",
|
|
5815
|
+
"baseUrl": "https://api.anthropic.com",
|
|
5816
|
+
"reasoning": true,
|
|
5817
|
+
"input": [
|
|
5818
|
+
"text",
|
|
5819
|
+
"image"
|
|
5820
|
+
],
|
|
5821
|
+
"cost": {
|
|
5822
|
+
"input": 2,
|
|
5823
|
+
"output": 10,
|
|
5824
|
+
"cacheRead": 0.2,
|
|
5825
|
+
"cacheWrite": 2.5
|
|
5826
|
+
},
|
|
5827
|
+
"contextWindow": 1000000,
|
|
5828
|
+
"maxTokens": 128000,
|
|
5829
|
+
"thinking": {
|
|
5830
|
+
"mode": "anthropic-adaptive",
|
|
5831
|
+
"minLevel": "minimal",
|
|
5832
|
+
"maxLevel": "max"
|
|
5833
|
+
}
|
|
5659
5834
|
}
|
|
5660
5835
|
},
|
|
5661
5836
|
"azure-openai": {
|
|
@@ -68026,6 +68201,41 @@
|
|
|
68026
68201
|
}
|
|
68027
68202
|
}
|
|
68028
68203
|
},
|
|
68204
|
+
"gpt-6.1-sol": {
|
|
68205
|
+
"id": "gpt-6.1-sol",
|
|
68206
|
+
"name": "GPT-6.1-Sol",
|
|
68207
|
+
"api": "openai-codex-responses",
|
|
68208
|
+
"provider": "openai-codex",
|
|
68209
|
+
"baseUrl": "https://chatgpt.com/backend-api",
|
|
68210
|
+
"reasoning": true,
|
|
68211
|
+
"input": [
|
|
68212
|
+
"text",
|
|
68213
|
+
"image"
|
|
68214
|
+
],
|
|
68215
|
+
"cost": {
|
|
68216
|
+
"input": 2,
|
|
68217
|
+
"output": 10,
|
|
68218
|
+
"cacheRead": 0.1,
|
|
68219
|
+
"cacheWrite": 2.5
|
|
68220
|
+
},
|
|
68221
|
+
"contextWindow": 272000,
|
|
68222
|
+
"maxTokens": 128000,
|
|
68223
|
+
"preferWebsockets": true,
|
|
68224
|
+
"thinking": {
|
|
68225
|
+
"mode": "effort",
|
|
68226
|
+
"minLevel": "low",
|
|
68227
|
+
"maxLevel": "max"
|
|
68228
|
+
},
|
|
68229
|
+
"longContextPricing": {
|
|
68230
|
+
"threshold": 272000,
|
|
68231
|
+
"cost": {
|
|
68232
|
+
"input": 4,
|
|
68233
|
+
"output": 15,
|
|
68234
|
+
"cacheRead": 0.2,
|
|
68235
|
+
"cacheWrite": 5
|
|
68236
|
+
}
|
|
68237
|
+
}
|
|
68238
|
+
},
|
|
68029
68239
|
"gpt-daybreak-blue-latest": {
|
|
68030
68240
|
"id": "gpt-daybreak-blue-latest",
|
|
68031
68241
|
"name": "Daybreak Blue",
|
|
@@ -1764,6 +1764,23 @@ function isTransientStreamEnvelopeError(error: unknown): boolean {
|
|
|
1764
1764
|
);
|
|
1765
1765
|
}
|
|
1766
1766
|
|
|
1767
|
+
/**
|
|
1768
|
+
* A request whose connection failed before the server returned any response:
|
|
1769
|
+
* the SDK's connection error, or a reset/closed/refused socket with no HTTP status.
|
|
1770
|
+
*/
|
|
1771
|
+
function isPreResponseConnectionFailure(error: unknown): boolean {
|
|
1772
|
+
if (!(error instanceof Error)) return false;
|
|
1773
|
+
if (extractHttpStatusFromError(error) !== undefined) return false;
|
|
1774
|
+
if (error instanceof Anthropic.APIConnectionTimeoutError) return false;
|
|
1775
|
+
if (error instanceof Anthropic.APIConnectionError) return true;
|
|
1776
|
+
const code = (error as { code?: unknown }).code;
|
|
1777
|
+
if (typeof code === "string" && /^(?:ECONNRESET|ECONNREFUSED|EPIPE|ENOTFOUND|EAI_AGAIN)$/.test(code)) return true;
|
|
1778
|
+
return (
|
|
1779
|
+
isUnexpectedSocketCloseMessage(error.message) ||
|
|
1780
|
+
/\b(?:ECONNRESET|ECONNREFUSED|EPIPE)\b|^connection error\.?$|other side closed/i.test(error.message)
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1767
1784
|
function isProviderRetryableStreamEnvelopeError(error: unknown): boolean {
|
|
1768
1785
|
if (!(error instanceof Error)) return false;
|
|
1769
1786
|
return /stream event order|before message_start/i.test(error.message);
|
|
@@ -2672,7 +2689,14 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
2672
2689
|
// Otherwise the multi-megabyte body is re-uploaded up to the default
|
|
2673
2690
|
// streamMaxRetries budget despite the ceiling. Once iteration has
|
|
2674
2691
|
// begun, only the grace-clock path above decides.
|
|
2675
|
-
|
|
2692
|
+
// A connection that dropped before any response (reset, socket closed,
|
|
2693
|
+
// connect failure) is exempt: the server never answered, so a retry is
|
|
2694
|
+
// not a re-upload after a stall, and a network blip must not end the turn.
|
|
2695
|
+
if (
|
|
2696
|
+
requestUploadCeilingBound &&
|
|
2697
|
+
firstEventWaitStartedAt === undefined &&
|
|
2698
|
+
!isPreResponseConnectionFailure(streamFailure)
|
|
2699
|
+
) {
|
|
2676
2700
|
Object.assign(streamFailure as Error, {
|
|
2677
2701
|
requestBytes,
|
|
2678
2702
|
endpointClass,
|
|
@@ -58,11 +58,15 @@ interface WireToolSpec {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
interface WireToolResult {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
toolUseId: string;
|
|
62
|
+
status: "success" | "error";
|
|
63
|
+
content: Array<{ text: string }>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface WireToolUse {
|
|
67
|
+
toolUseId: string;
|
|
68
|
+
name: string;
|
|
69
|
+
input: Record<string, unknown>;
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
interface WireUserMessage {
|
|
@@ -71,7 +75,7 @@ interface WireUserMessage {
|
|
|
71
75
|
modelId?: string;
|
|
72
76
|
userInputMessageContext?: {
|
|
73
77
|
tools?: { tools: WireToolSpec[] };
|
|
74
|
-
toolResults?:
|
|
78
|
+
toolResults?: WireToolResult[];
|
|
75
79
|
editorStateContext?: Record<string, unknown>;
|
|
76
80
|
};
|
|
77
81
|
origin?: string;
|
|
@@ -81,6 +85,7 @@ interface WireUserMessage {
|
|
|
81
85
|
interface WireAssistantMessage {
|
|
82
86
|
assistantResponseMessage: {
|
|
83
87
|
content: string;
|
|
88
|
+
toolUses?: WireToolUse[];
|
|
84
89
|
};
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -383,6 +388,21 @@ function buildConversationState(
|
|
|
383
388
|
|
|
384
389
|
for (let i = 0; i < messages.length - 1; i++) {
|
|
385
390
|
const msg = messages[i];
|
|
391
|
+
const previous = history[history.length - 1];
|
|
392
|
+
// Parallel tool calls produce consecutive toolResult messages; they answer one
|
|
393
|
+
// assistant turn, so their results share a single user entry.
|
|
394
|
+
if (
|
|
395
|
+
msg.role === "toolResult" &&
|
|
396
|
+
messages[i - 1]?.role === "toolResult" &&
|
|
397
|
+
previous &&
|
|
398
|
+
"userInputMessage" in previous
|
|
399
|
+
) {
|
|
400
|
+
previous.userInputMessage.userInputMessageContext ??= {};
|
|
401
|
+
const context = previous.userInputMessage.userInputMessageContext;
|
|
402
|
+
context.toolResults ??= [];
|
|
403
|
+
context.toolResults.push(convertToolResult(msg as ToolResultMessage));
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
386
406
|
history.push(convertToWireMessage(msg, modelId, i === 0 ? systemPrompt : undefined));
|
|
387
407
|
}
|
|
388
408
|
|
|
@@ -419,20 +439,27 @@ function convertToWireMessage(
|
|
|
419
439
|
if (msg.role === "toolResult") {
|
|
420
440
|
return convertToWireUserMessage(msg, modelId, systemPrompt);
|
|
421
441
|
}
|
|
422
|
-
// assistant → assistant response
|
|
442
|
+
// assistant → assistant response; tool calls stay structured so later tool
|
|
443
|
+
// results can reference the toolUseId they answer.
|
|
423
444
|
const textParts: string[] = [];
|
|
445
|
+
const toolUses: WireToolUse[] = [];
|
|
424
446
|
for (const block of msg.content) {
|
|
425
447
|
if (typeof block === "string") {
|
|
426
448
|
textParts.push(block);
|
|
427
449
|
} else if (block.type === "text") {
|
|
428
450
|
textParts.push(block.text);
|
|
429
451
|
} else if (block.type === "toolCall") {
|
|
430
|
-
|
|
452
|
+
toolUses.push({
|
|
453
|
+
toolUseId: block.id,
|
|
454
|
+
name: block.name,
|
|
455
|
+
input: (block.arguments ?? {}) as Record<string, unknown>,
|
|
456
|
+
});
|
|
431
457
|
}
|
|
432
458
|
}
|
|
433
459
|
return {
|
|
434
460
|
assistantResponseMessage: {
|
|
435
|
-
content: textParts.join("\n")
|
|
461
|
+
content: textParts.join("\n"),
|
|
462
|
+
...(toolUses.length > 0 ? { toolUses } : {}),
|
|
436
463
|
},
|
|
437
464
|
};
|
|
438
465
|
}
|
|
@@ -442,7 +469,9 @@ function convertToWireUserMessage(
|
|
|
442
469
|
modelId: string,
|
|
443
470
|
systemPrompt?: string,
|
|
444
471
|
): WireUserMessage {
|
|
445
|
-
|
|
472
|
+
// A tool result travels in userInputMessageContext.toolResults; repeating it as
|
|
473
|
+
// message text would show the model each result twice.
|
|
474
|
+
let content = msg.role === "toolResult" ? "Tool results provided." : extractTextContent(msg);
|
|
446
475
|
if (systemPrompt) {
|
|
447
476
|
content = `${systemPrompt}\n\n${content}`;
|
|
448
477
|
}
|
|
@@ -454,27 +483,28 @@ function convertToWireUserMessage(
|
|
|
454
483
|
},
|
|
455
484
|
};
|
|
456
485
|
|
|
457
|
-
// Handle tool results
|
|
458
486
|
if (msg.role === "toolResult") {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
content: detail.type === "text" ? detail.text : "",
|
|
463
|
-
toolUseId: toolResultMsg.toolCallId,
|
|
464
|
-
status: toolResultMsg.isError ? ("error" as const) : ("success" as const),
|
|
465
|
-
},
|
|
466
|
-
}));
|
|
467
|
-
if (toolResults.length > 0) {
|
|
468
|
-
if (!userMsg.userInputMessage.userInputMessageContext) {
|
|
469
|
-
userMsg.userInputMessage.userInputMessageContext = {};
|
|
470
|
-
}
|
|
471
|
-
userMsg.userInputMessage.userInputMessageContext.toolResults = { toolResults: [toolResults] };
|
|
472
|
-
}
|
|
487
|
+
userMsg.userInputMessage.userInputMessageContext = {
|
|
488
|
+
toolResults: [convertToolResult(msg as ToolResultMessage)],
|
|
489
|
+
};
|
|
473
490
|
}
|
|
474
491
|
|
|
475
492
|
return userMsg;
|
|
476
493
|
}
|
|
477
494
|
|
|
495
|
+
/** One CodeWhisperer tool result per tool call, keyed by the toolUseId it answers. */
|
|
496
|
+
function convertToolResult(msg: ToolResultMessage): WireToolResult {
|
|
497
|
+
const text = (msg.content ?? [])
|
|
498
|
+
.map(block => (block.type === "text" ? block.text : block.type === "image" ? "[image omitted]" : ""))
|
|
499
|
+
.filter(part => part.length > 0)
|
|
500
|
+
.join("\n");
|
|
501
|
+
return {
|
|
502
|
+
toolUseId: msg.toolCallId,
|
|
503
|
+
status: msg.isError ? "error" : "success",
|
|
504
|
+
content: [{ text }],
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
478
508
|
function extractTextContent(msg: Context["messages"][number]): string {
|
|
479
509
|
if (typeof msg.content === "string") return msg.content;
|
|
480
510
|
if (Array.isArray(msg.content)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { scheduler } from "node:timers/promises";
|
|
2
2
|
import { $credentialEnv, $env, extractHttpStatusFromError, logger } from "@gajae-code/utils";
|
|
3
|
-
import OpenAI, { APIConnectionTimeoutError } from "openai";
|
|
3
|
+
import OpenAI, { APIConnectionTimeoutError, type ClientOptions as OpenAIClientOptions } from "openai";
|
|
4
4
|
import type {
|
|
5
5
|
ChatCompletionAssistantMessageParam,
|
|
6
6
|
ChatCompletionChunk,
|
|
@@ -1509,6 +1509,22 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
|
|
|
1509
1509
|
return stream;
|
|
1510
1510
|
};
|
|
1511
1511
|
|
|
1512
|
+
/**
|
|
1513
|
+
* The OpenAI SDK retries pre-header timeouts and connection failures silently
|
|
1514
|
+
* (its retry lines log at `info`, below the default `warn`, and go to console,
|
|
1515
|
+
* which corrupts the TUI). Route them into the gjc log so a request stuck
|
|
1516
|
+
* before response headers — where our stream watchdogs are not armed yet —
|
|
1517
|
+
* leaves a trace instead of looking like a hung turn.
|
|
1518
|
+
*/
|
|
1519
|
+
const OPENAI_SDK_LOGGER: NonNullable<OpenAIClientOptions["logger"]> = {
|
|
1520
|
+
error: message => logger.warn(`openai-sdk: ${message}`),
|
|
1521
|
+
warn: message => logger.warn(`openai-sdk: ${message}`),
|
|
1522
|
+
info: message => {
|
|
1523
|
+
if (/retrying|timed out|connection failed/.test(message)) logger.warn(`openai-sdk: ${message}`);
|
|
1524
|
+
},
|
|
1525
|
+
debug: () => {},
|
|
1526
|
+
};
|
|
1527
|
+
|
|
1512
1528
|
async function createClient(
|
|
1513
1529
|
model: Model<"openai-completions">,
|
|
1514
1530
|
context: Context,
|
|
@@ -1680,6 +1696,8 @@ async function createClient(
|
|
|
1680
1696
|
defaultHeaders: headers,
|
|
1681
1697
|
fetch: debugFetch,
|
|
1682
1698
|
...(sdkTimeoutMs !== undefined ? { timeout: sdkTimeoutMs } : {}),
|
|
1699
|
+
logLevel: "info",
|
|
1700
|
+
logger: OPENAI_SDK_LOGGER,
|
|
1683
1701
|
}),
|
|
1684
1702
|
copilotPremiumRequests,
|
|
1685
1703
|
baseUrl,
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Patch `globalThis.fetch` to advertise HTTP/2 in TLS ALPN, with transparent
|
|
3
|
-
* HTTP/1.1 fallback when the server doesn't negotiate `h2`.
|
|
4
|
-
*
|
|
5
|
-
* Bun's HTTP/2 client is gated on `BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT`,
|
|
6
|
-
* read by the native runtime before any JS executes; assigning to
|
|
7
|
-
* `process.env` from inside JS is a no-op. Per-request `protocol: "http2"`
|
|
8
|
-
* activates h2 over TLS ALPN and rejects with `error.code === "HTTP2Unsupported"`
|
|
9
|
-
* if the server picks anything else, so we catch and retry without the hint.
|
|
10
|
-
*
|
|
11
|
-
* Some HTTPS endpoints (e.g. corporate API gateways behind reverse proxies)
|
|
12
|
-
* advertise h2 via ALPN but then refuse or reset the connection at the HTTP/2
|
|
13
|
-
* framing layer. Bun surfaces these as `ConnectionRefused`, `ConnectionReset`,
|
|
14
|
-
* `ConnectionClosed`, or `HTTP2StreamReset` rather than `HTTP2Unsupported`.
|
|
15
|
-
* `ConnectionRefused` is raised before the request is written, while
|
|
16
|
-
* `HTTP2RefusedStream` is the explicit HTTP/2 promise that a stream was never
|
|
17
|
-
* processed (including a stream above a graceful GOAWAY last-stream-id).
|
|
18
|
-
* `ConnectionReset`, `ConnectionClosed`, and a generic `HTTP2StreamReset` do
|
|
19
|
-
* not carry that promise: the peer may have consumed the body before the
|
|
20
|
-
* connection failed. Retrying those codes would duplicate non-idempotent side
|
|
21
|
-
* effects, so they preserve the original error instead of falling back.
|
|
22
|
-
*
|
|
23
|
-
* ALPN-refusing hosts (notably zcode.z.ai, the GLM ZCode OAuth broker) abort
|
|
24
|
-
* the TLS handshake entirely when the client offers ALPN h2. Bun reports that
|
|
25
|
-
* abort as `UNKNOWN_CERTIFICATE_VERIFICATION_ERROR` even though the host's
|
|
26
|
-
* certificate chain verifies fine over h1 (issue #5178), so that code is a
|
|
27
|
-
* fallback trigger too — never a reason to accept a bad certificate: the h1
|
|
28
|
-
* attempt below performs full verification on its own.
|
|
29
|
-
*
|
|
30
|
-
* Bun negotiates h2 via ALPN over TLS only (no h2c), so plain `http://` URLs
|
|
31
|
-
* skip the attempt entirely — avoids the throw/retry round-trip for localhost.
|
|
32
|
-
*
|
|
33
|
-
* Idempotent.
|
|
34
|
-
*/
|
|
35
|
-
export declare function installH2Fetch(): void;
|
package/src/utils/h2-fetch.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Patch `globalThis.fetch` to advertise HTTP/2 in TLS ALPN, with transparent
|
|
3
|
-
* HTTP/1.1 fallback when the server doesn't negotiate `h2`.
|
|
4
|
-
*
|
|
5
|
-
* Bun's HTTP/2 client is gated on `BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT`,
|
|
6
|
-
* read by the native runtime before any JS executes; assigning to
|
|
7
|
-
* `process.env` from inside JS is a no-op. Per-request `protocol: "http2"`
|
|
8
|
-
* activates h2 over TLS ALPN and rejects with `error.code === "HTTP2Unsupported"`
|
|
9
|
-
* if the server picks anything else, so we catch and retry without the hint.
|
|
10
|
-
*
|
|
11
|
-
* Some HTTPS endpoints (e.g. corporate API gateways behind reverse proxies)
|
|
12
|
-
* advertise h2 via ALPN but then refuse or reset the connection at the HTTP/2
|
|
13
|
-
* framing layer. Bun surfaces these as `ConnectionRefused`, `ConnectionReset`,
|
|
14
|
-
* `ConnectionClosed`, or `HTTP2StreamReset` rather than `HTTP2Unsupported`.
|
|
15
|
-
* `ConnectionRefused` is raised before the request is written, while
|
|
16
|
-
* `HTTP2RefusedStream` is the explicit HTTP/2 promise that a stream was never
|
|
17
|
-
* processed (including a stream above a graceful GOAWAY last-stream-id).
|
|
18
|
-
* `ConnectionReset`, `ConnectionClosed`, and a generic `HTTP2StreamReset` do
|
|
19
|
-
* not carry that promise: the peer may have consumed the body before the
|
|
20
|
-
* connection failed. Retrying those codes would duplicate non-idempotent side
|
|
21
|
-
* effects, so they preserve the original error instead of falling back.
|
|
22
|
-
*
|
|
23
|
-
* ALPN-refusing hosts (notably zcode.z.ai, the GLM ZCode OAuth broker) abort
|
|
24
|
-
* the TLS handshake entirely when the client offers ALPN h2. Bun reports that
|
|
25
|
-
* abort as `UNKNOWN_CERTIFICATE_VERIFICATION_ERROR` even though the host's
|
|
26
|
-
* certificate chain verifies fine over h1 (issue #5178), so that code is a
|
|
27
|
-
* fallback trigger too — never a reason to accept a bad certificate: the h1
|
|
28
|
-
* attempt below performs full verification on its own.
|
|
29
|
-
*
|
|
30
|
-
* Bun negotiates h2 via ALPN over TLS only (no h2c), so plain `http://` URLs
|
|
31
|
-
* skip the attempt entirely — avoids the throw/retry round-trip for localhost.
|
|
32
|
-
*
|
|
33
|
-
* Idempotent.
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
const installed: unique symbol = Symbol.for("gajae-code.h2fetch.installed");
|
|
37
|
-
|
|
38
|
-
interface PatchedFetch {
|
|
39
|
-
[installed]?: true;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function installH2Fetch(): void {
|
|
43
|
-
const original = globalThis.fetch as typeof fetch & PatchedFetch;
|
|
44
|
-
if (original[installed]) return;
|
|
45
|
-
|
|
46
|
-
/** Error codes that indicate h2 negotiation/transport failure (not an application error). */
|
|
47
|
-
const h2FallbackCodes: ReadonlySet<string> = new Set([
|
|
48
|
-
"HTTP2Unsupported", // Server selected h1 in ALPN
|
|
49
|
-
"ConnectionRefused", // Server refused the h2 connection
|
|
50
|
-
"HTTP2RefusedStream", // REFUSED_STREAM / never-processed h2 stream
|
|
51
|
-
// Bun's h2 client reports an ALPN-refusing host's TLS abort with this
|
|
52
|
-
// code; the h1 fallback below re-verifies the certificate itself.
|
|
53
|
-
"UNKNOWN_CERTIFICATE_VERIFICATION_ERROR",
|
|
54
|
-
]);
|
|
55
|
-
const wrapper = async function h2fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> {
|
|
56
|
-
if (!isHttps(input)) return original(input, init);
|
|
57
|
-
const snapshot = snapshotRequest(input, init);
|
|
58
|
-
try {
|
|
59
|
-
return await original(snapshot.input, { ...snapshot.init, protocol: "http2" });
|
|
60
|
-
} catch (err) {
|
|
61
|
-
const code = (err as { code?: string }).code ?? "";
|
|
62
|
-
if (!h2FallbackCodes.has(code)) throw err;
|
|
63
|
-
if (code === "HTTP2RefusedStream" && !isReplayableRequest(snapshot.input, snapshot.init)) throw err;
|
|
64
|
-
return original(snapshot.input, snapshot.init);
|
|
65
|
-
}
|
|
66
|
-
} as typeof fetch & PatchedFetch;
|
|
67
|
-
|
|
68
|
-
// Preserve `fetch.preconnect` and any other statics SDK code might poke at.
|
|
69
|
-
Object.assign(wrapper, original);
|
|
70
|
-
wrapper[installed] = true;
|
|
71
|
-
globalThis.fetch = wrapper;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function snapshotRequest(
|
|
75
|
-
input: string | URL | Request,
|
|
76
|
-
init?: RequestInit,
|
|
77
|
-
): { input: string | URL | Request; init?: RequestInit } {
|
|
78
|
-
const snapshotInput = input instanceof URL ? new URL(input.href) : input;
|
|
79
|
-
const requestLike = typeof input !== "string" && !(input instanceof URL);
|
|
80
|
-
if (init === undefined && !requestLike) {
|
|
81
|
-
return { input: snapshotInput };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const snapshotInit: RequestInit = { ...(init ?? {}) };
|
|
85
|
-
if (init?.headers !== undefined) {
|
|
86
|
-
snapshotInit.headers = snapshotHeaders(init.headers);
|
|
87
|
-
} else if (requestLike) {
|
|
88
|
-
snapshotInit.headers = new Headers(input.headers);
|
|
89
|
-
}
|
|
90
|
-
return { input: snapshotInput, init: snapshotInit };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function snapshotHeaders(headers: NonNullable<RequestInit["headers"]>): NonNullable<RequestInit["headers"]> {
|
|
94
|
-
if (headers instanceof Headers) return new Headers(headers);
|
|
95
|
-
if (Array.isArray(headers)) {
|
|
96
|
-
return headers.map(([name, value]) => [name, value] as [string, string]);
|
|
97
|
-
}
|
|
98
|
-
return { ...headers };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function isHttps(input: string | URL | Request): boolean {
|
|
102
|
-
if (typeof input === "string") return input.startsWith("https:");
|
|
103
|
-
if (input instanceof URL) return input.protocol === "https:";
|
|
104
|
-
return input.url.startsWith("https:");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Whether the request body can be handed to fetch a second time. This is only
|
|
109
|
-
* used after `HTTP2RefusedStream`, which proves that the peer did not consume
|
|
110
|
-
* request bytes; a one-shot ReadableStream is still not reusable locally.
|
|
111
|
-
*/
|
|
112
|
-
function isReplayableRequest(input: string | URL | Request, init?: RequestInit): boolean {
|
|
113
|
-
try {
|
|
114
|
-
if (init?.body !== undefined) return isReplayableBody(init.body);
|
|
115
|
-
if (typeof input === "string" || input instanceof URL) return true;
|
|
116
|
-
// Request bodies are exposed as ReadableStreams. The same Request object
|
|
117
|
-
// cannot be used for the second fetch once the first attempt touched it.
|
|
118
|
-
return input.body === null;
|
|
119
|
-
} catch {
|
|
120
|
-
// Cross-realm or proxy Request objects may throw while exposing their
|
|
121
|
-
// method/body. Do not retry when replayability cannot be established.
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function isReplayableBody(body: RequestInit["body"]): boolean {
|
|
127
|
-
if (body === null || typeof body === "string") return true;
|
|
128
|
-
if (typeof Blob !== "undefined" && body instanceof Blob) return true;
|
|
129
|
-
// ArrayBuffer/views, FormData, and URLSearchParams are mutable caller-owned
|
|
130
|
-
// values. Fail closed instead of replaying a potentially changed payload if
|
|
131
|
-
// the asynchronous refusal arrives after the caller mutates one.
|
|
132
|
-
return false;
|
|
133
|
-
}
|