@ottocode/sdk 0.1.197 → 0.1.199

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.197",
3
+ "version": "0.1.199",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -435,6 +435,7 @@ function applyHunkToLines(
435
435
  let matchIndex = hasExpected
436
436
  ? findSubsequence(lines, expected, Math.max(0, initialHint - 3), useFuzzy)
437
437
  : -1;
438
+ let matchedExpected = expected;
438
439
 
439
440
  if (hasExpected && matchIndex === -1) {
440
441
  matchIndex = findSubsequence(lines, expected, 0, useFuzzy);
@@ -447,11 +448,24 @@ function applyHunkToLines(
447
448
  if (!allContextPresent) {
448
449
  matchIndex = -1;
449
450
  } else {
450
- const expectedWithoutMissingRemovals = expected.filter((line) =>
451
- lineExists(lines, line, useFuzzy),
452
- );
453
- const minRequired = Math.max(2, Math.ceil(expected.length / 2));
454
- if (expectedWithoutMissingRemovals.length >= minRequired) {
451
+ const expectedWithoutMissingRemovals = hunk.lines
452
+ .filter((line) => {
453
+ if (line.kind === 'add') return false;
454
+ if (line.kind === 'remove') {
455
+ return lineExists(lines, line.content, useFuzzy);
456
+ }
457
+ return true;
458
+ })
459
+ .map((line) => line.content);
460
+ const includedRemovalCount = hunk.lines.filter(
461
+ (line) =>
462
+ line.kind === 'remove' && lineExists(lines, line.content, useFuzzy),
463
+ ).length;
464
+ const minRequired = Math.max(contextLines.length, 2);
465
+ if (
466
+ includedRemovalCount > 0 &&
467
+ expectedWithoutMissingRemovals.length >= minRequired
468
+ ) {
455
469
  matchIndex = findSubsequence(
456
470
  lines,
457
471
  expectedWithoutMissingRemovals,
@@ -466,6 +480,63 @@ function applyHunkToLines(
466
480
  useFuzzy,
467
481
  );
468
482
  }
483
+ if (matchIndex !== -1) {
484
+ matchedExpected = expectedWithoutMissingRemovals;
485
+ }
486
+ }
487
+ }
488
+ }
489
+
490
+ if (
491
+ matchIndex === -1 &&
492
+ useFuzzy &&
493
+ removals.length >= 2 &&
494
+ contextLines.length === 0
495
+ ) {
496
+ const firstRemoval = removals[0].content;
497
+ const lastRemoval = removals[removals.length - 1].content;
498
+ const firstIdx = findLineIndex(lines, firstRemoval, 0, true);
499
+ if (firstIdx !== -1) {
500
+ const rangeEnd = firstIdx + expected.length - 1;
501
+ if (rangeEnd < lines.length) {
502
+ const lastInRange = lines[rangeEnd];
503
+ let lastMatches = lastInRange === lastRemoval;
504
+ if (!lastMatches) {
505
+ for (const level of NORMALIZATION_LEVELS.slice(1)) {
506
+ if (
507
+ normalizeWhitespace(lastInRange, level) ===
508
+ normalizeWhitespace(lastRemoval, level)
509
+ ) {
510
+ lastMatches = true;
511
+ break;
512
+ }
513
+ }
514
+ }
515
+ if (lastMatches) {
516
+ let matchCount = 0;
517
+ for (let k = 0; k < expected.length; k++) {
518
+ const fileLine = lines[firstIdx + k];
519
+ const expLine = expected[k];
520
+ if (fileLine === expLine) {
521
+ matchCount++;
522
+ continue;
523
+ }
524
+ for (const level of NORMALIZATION_LEVELS.slice(1)) {
525
+ if (
526
+ normalizeWhitespace(fileLine, level) ===
527
+ normalizeWhitespace(expLine, level)
528
+ ) {
529
+ matchCount++;
530
+ break;
531
+ }
532
+ }
533
+ }
534
+ const matchRatio = matchCount / expected.length;
535
+ if (matchRatio >= 0.5) {
536
+ matchIndex = firstIdx;
537
+ matchedExpected = lines.slice(firstIdx, firstIdx + expected.length);
538
+ }
539
+ }
469
540
  }
470
541
  }
471
542
  }
@@ -552,7 +623,7 @@ function applyHunkToLines(
552
623
  throw new Error(errorMsg);
553
624
  }
554
625
 
555
- const deleteCount = hasExpected ? expected.length : 0;
626
+ const deleteCount = hasExpected ? matchedExpected.length : 0;
556
627
  const originalIndex = matchIndex;
557
628
  const oldStart = Math.min(
558
629
  originalLines.length,
@@ -561,10 +632,10 @@ function applyHunkToLines(
561
632
  const newStart = matchIndex + 1;
562
633
 
563
634
  const adjustedReplacement =
564
- useFuzzy && hasExpected
635
+ useFuzzy && hasExpected && matchedExpected.length === expected.length
565
636
  ? adjustReplacementIndentation(
566
637
  hunk,
567
- lines.slice(matchIndex, matchIndex + expected.length),
638
+ lines.slice(matchIndex, matchIndex + matchedExpected.length),
568
639
  originalLines,
569
640
  )
570
641
  : replacement;
@@ -14,7 +14,7 @@ export function parsePatchInput(patch: string): {
14
14
  throw new Error('Patch content is empty.');
15
15
  }
16
16
 
17
- if (trimmed.includes(PATCH_BEGIN_MARKER)) {
17
+ if (trimmed.startsWith(PATCH_BEGIN_MARKER)) {
18
18
  return {
19
19
  format: 'enveloped',
20
20
  operations: parseEnvelopedPatch(patch),
@@ -37,7 +37,8 @@ function appendMissingEndMarker(patch: string): string {
37
37
  const hasContent =
38
38
  trimmed.includes('*** Update File:') ||
39
39
  trimmed.includes('*** Add File:') ||
40
- trimmed.includes('*** Delete File:');
40
+ trimmed.includes('*** Delete File:') ||
41
+ trimmed.includes('*** Replace in:');
41
42
 
42
43
  if (hasContent) {
43
44
  return `${trimmed}\n${PATCH_END_MARKER}`;
@@ -1,207 +1,94 @@
1
- Apply a patch to modify one or more files. The tool accepts the **enveloped patch format** (preferred) and will also auto-convert standard unified diffs (`--- / +++`) if you provide one.
1
+ Apply a patch to modify one or more files.
2
2
 
3
- **Quick checklist before you call the tool:**
4
- - Finished patch must include both `*** Begin Patch` and `*** End Patch`
5
- - Each change needs a directive (`*** Add File`, `*** Update File`, `*** Delete File`)
6
- - Include real context lines (prefixed with space) around your changes
7
- - Keep paths relative to the project root
8
- - **Use multiple `@@` hunks for multiple edits to the same file - do NOT make separate tool calls**
3
+ Use the **enveloped format** by default. Standard unified diffs (`---` / `+++`) are also accepted.
9
4
 
10
- **RECOMMENDED: Use apply_patch for targeted file edits to avoid rewriting entire files and wasting tokens.**
5
+ ## Fastest / safest mode (recommended): Replace
11
6
 
12
- **FUZZY MATCHING**: By default, fuzzy matching is enabled to handle whitespace differences (tabs vs spaces).
13
- Exact matching is tried first, then normalized matching if exact fails. Disable with `fuzzyMatch: false` if needed.
7
+ Use this when exact `+/-/context` hunks are hard to build.
14
8
 
15
- Use `apply_patch` only when:
16
- - You want to make targeted edits to specific lines (primary use case)
17
- - You want to make multiple related changes across different files in a single operation
18
- - You need to add/delete entire files along with modifications
19
- - You have JUST read the file immediately before (within the same response) and are confident the content hasn't changed
20
-
21
- **CRITICAL - ALWAYS READ BEFORE PATCHING**: You MUST read the file content immediately before creating a patch.
22
- Never rely on memory or previous reads. Even with fuzzy matching enabled (tolerates tabs vs spaces),
23
- If the file content has changed significantly since you last read it, the patch may still fail.
24
-
25
- **ALLOW REJECTS**: If you are applying a patch that might include stale hunks, set `allowRejects: true`.
26
- The tool will apply the hunks it can and skip the rest, returning the skipped hunks with reasons.
27
-
28
- **ALREADY APPLIED?**: If the removal lines are already gone or the additions are already present, the tool will treat the hunk as applied and move on—no need to resend the same change.
29
-
30
- **Alternative: Use the `edit` tool if you need fuzzy matching or structured operations.**
31
-
32
- ## ⚠️ CRITICAL: Multiple Edits to Same File
33
-
34
- **DO NOT make separate `apply_patch` calls for the same file!**
35
-
36
- Instead, use multiple `@@` hunks in a single patch:
37
-
38
- ```
9
+ ```text
39
10
  *** Begin Patch
40
- *** Update File: src/app.ts
41
- @@ first change - line 10
42
- function init() {
43
- - const port = 3000;
44
- + const port = 8080;
45
- return port;
46
- }
47
- @@ second change - line 25
48
- function start() {
49
- - console.log("Starting...");
50
- + console.log("Server starting...");
51
- init();
52
- }
53
- @@ third change - line 40
54
- function cleanup() {
55
- - // TODO
56
- + console.log("Cleaning up...");
57
- }
11
+ *** Replace in: path/to/file.ts
12
+ *** Find:
13
+ old text block
14
+ *** With:
15
+ new text block
58
16
  *** End Patch
59
17
  ```
60
18
 
61
- **This makes ONE tool call instead of three, saving tokens and reducing latency.**
62
-
63
- ## Patch Formats
19
+ You can include multiple `*** Find:` / `*** With:` pairs under one `*** Replace in:` block.
64
20
 
65
- ### Preferred: Enveloped Patch
21
+ ---
66
22
 
67
- All patches must be wrapped in markers and use explicit file directives:
23
+ ## Standard mode: Add / Update / Delete
68
24
 
69
- ```
25
+ ```text
70
26
  *** Begin Patch
71
- *** Add File: path/to/file.txt
72
- +line 1
73
- +line 2
74
- *** Update File: path/to/other.txt
27
+ *** Add File: path/to/new.txt
28
+ +new content
29
+ *** Update File: path/to/existing.ts
30
+ context line
75
31
  -old line
76
32
  +new line
77
- *** Delete File: path/to/delete.txt
33
+ *** Delete File: path/to/old.txt
78
34
  *** End Patch
79
35
  ```
80
36
 
81
- ### Also Supported: Standard Unified Diff
37
+ For multiple edits in the same file, use **one** `*** Update File:` block with multiple `@@` hunks.
82
38
 
83
- You can paste a regular `git diff` style patch:
39
+ ---
84
40
 
85
- ```
86
- diff --git a/src/app.ts b/src/app.ts
87
- --- a/src/app.ts
88
- +++ b/src/app.ts
89
- @@
90
- -const PORT = 3000;
91
- +const PORT = 8080;
92
- ```
41
+ ## Rules that prevent failures
93
42
 
94
- The tool will convert it automatically, but the enveloped format is still recommended because it is more explicit and easier to control.
43
+ 1. Patch must start with `*** Begin Patch` and end with `*** End Patch`.
44
+ 2. Paths must be relative to project root.
45
+ 3. In `Update File`, `-` lines must match file content exactly.
46
+ 4. Include real context lines (prefix with a single space ` `).
47
+ 5. `@@` lines are only hints; they do not replace real context.
48
+ 6. Do not make separate `apply_patch` calls for the same file in one task.
95
49
 
96
- ## File Operations
50
+ ---
97
51
 
98
- ### Add a new file:
99
- ```
100
- *** Begin Patch
101
- *** Add File: src/hello.ts
102
- +export function hello() {
103
- + console.log("Hello!");
104
- +}
105
- *** End Patch
106
- ```
52
+ ## Matching behavior
107
53
 
108
- ### Update an existing file (simple replacement):
109
- ```
110
- *** Begin Patch
111
- *** Update File: src/config.ts
112
- -const PORT = 3000;
113
- +const PORT = 8080;
114
- *** End Patch
115
- ```
54
+ - `fuzzyMatch` defaults to `true` (helps with small whitespace differences).
55
+ - Set `fuzzyMatch: false` for strict matching.
56
+ - If a hunk may be stale, set `allowRejects: true` to apply valid parts and skip invalid ones.
116
57
 
117
- **CRITICAL**: The `-` lines must match EXACTLY what's in the file, character-for-character. If you're not 100% certain, use the `edit` tool instead.
58
+ ---
118
59
 
119
- ### Update with context (recommended for precision):
120
- ```
121
- *** Begin Patch
122
- *** Update File: src/app.ts
123
- @@ function main()
124
- function main() {
125
- - console.log("old");
126
- + console.log("new");
127
- }
128
- *** End Patch
129
- ```
60
+ ## Common errors
130
61
 
131
- **IMPORTANT**:
132
- - The `@@` line is an OPTIONAL hint to help locate the change - it's a comment, not parsed as context
133
- - REQUIRED: Actual context lines (starting with space ` `) that match the file exactly
134
- - The context lines with space prefix are what the tool uses to find the location
135
- - The `@@` line just helps humans/AI understand what section you're editing
62
+ - **Missing end marker** → add `*** End Patch`.
63
+ - **Failed to find expected lines** re-read the file and rebuild patch with exact context.
64
+ - **Ambiguous update** provide longer context or use `*** Replace in:` mode.
136
65
 
66
+ ---
137
67
 
138
- ### Update multiple locations in the same file:
139
- ```
68
+ ## Minimal templates
69
+
70
+ ### Update one line
71
+ ```text
140
72
  *** Begin Patch
141
73
  *** Update File: src/app.ts
142
- @@ first section - near line 10
143
- function init() {
144
- - const port = 3000;
145
- + const port = 8080;
146
- return port;
147
- }
148
- @@ second section - near line 25
149
- function start() {
150
- - console.log("Starting...");
151
- + console.log("Server starting...");
152
- init();
74
+ function main() {
75
+ - console.log("old");
76
+ + console.log("new");
153
77
  }
154
78
  *** End Patch
155
79
  ```
156
80
 
157
- **IMPORTANT**: Use separate `@@` headers for each non-consecutive change location. This allows multiple edits to the same file in one patch, saving tokens and reducing tool calls.
158
-
159
- ### Delete a file:
160
- ```
81
+ ### Add file
82
+ ```text
161
83
  *** Begin Patch
162
- *** Delete File: old/unused.ts
84
+ *** Add File: src/new.ts
85
+ +export const ok = true;
163
86
  *** End Patch
164
87
  ```
165
88
 
166
- ### Multiple operations in one patch:
167
- ```
89
+ ### Delete file
90
+ ```text
168
91
  *** Begin Patch
169
- *** Add File: new.txt
170
- +New content
171
- *** Update File: existing.txt
172
- -old
173
- +new
174
- *** Delete File: obsolete.txt
92
+ *** Delete File: src/obsolete.ts
175
93
  *** End Patch
176
94
  ```
177
-
178
- ## Line Prefixes
179
-
180
- - Lines starting with `+` are added
181
- - Lines starting with `-` are removed
182
- - Lines starting with ` ` (space) are context (kept unchanged)
183
- - Lines starting with `@@` are optional hints/comments (not parsed as context)
184
-
185
- ## Common Errors
186
-
187
- **"Missing *** End Patch marker"**: Every patch MUST end with `*** End Patch` on its own line.
188
- This is the most common error. Double-check your patch ends with:
189
- ```
190
- *** End Patch
191
- ```
192
-
193
- **"Failed to find expected lines"**: The file content doesn't match your patch. Common causes:
194
- - Missing context lines (lines with space prefix)
195
- - Using `@@` line as context instead of real context lines
196
- - The file content has changed since you read it
197
- - Whitespace/indentation mismatch
198
-
199
- **Solution**: Always read the file immediately before patching and include actual context lines with space prefix.
200
-
201
- ## Important Notes
202
-
203
- - **Patches are fragile**: Any mismatch in whitespace, indentation, or content will cause failure
204
- - **Use `edit` for reliability**: The `edit` tool can make targeted changes without requiring exact matches
205
- - All file paths are relative to the project root
206
- - The enveloped format is the most reliable; unified diffs are converted automatically if provided
207
- - Always wrap patches with `*** Begin Patch` and `*** End Patch` when you write them manually
package/src/index.ts CHANGED
@@ -70,6 +70,8 @@ export {
70
70
  export type {
71
71
  SetuAuth,
72
72
  SetuProviderOptions,
73
+ SetuPaymentCallbacks,
74
+ SetuBalanceUpdate,
73
75
  SetuBalanceResponse,
74
76
  SolanaUsdcBalanceResponse,
75
77
  } from './providers/src/index.ts';
@@ -30,6 +30,12 @@ const isAllowedGoogleModel = (id: string): boolean => {
30
30
  return false;
31
31
  };
32
32
 
33
+ const isAllowedZaiModel = (id: string): boolean => {
34
+ if (id.startsWith('glm-4.7')) return true;
35
+ if (id.startsWith('glm-5')) return true;
36
+ return false;
37
+ };
38
+
33
39
  const SETU_SOURCES: Array<{
34
40
  id: ProviderId;
35
41
  npm: string;
@@ -55,6 +61,11 @@ const SETU_SOURCES: Array<{
55
61
  npm: '@ai-sdk/google',
56
62
  family: 'google',
57
63
  },
64
+ {
65
+ id: 'zai',
66
+ npm: '@ai-sdk/openai-compatible',
67
+ family: 'openai-compatible',
68
+ },
58
69
  ];
59
70
 
60
71
  function cloneModel(model: ModelInfo): ModelInfo {
@@ -83,6 +94,7 @@ function buildSetuEntry(base: CatalogMap): ProviderCatalogEntry | null {
83
94
  if (id === 'openai') return isAllowedOpenAIModel(model.id);
84
95
  if (id === 'anthropic') return isAllowedAnthropicModel(model.id);
85
96
  if (id === 'google') return isAllowedGoogleModel(model.id);
97
+ if (id === 'zai') return isAllowedZaiModel(model.id);
86
98
  return true;
87
99
  });
88
100
  return sourceModels.map((model) => {
@@ -703,6 +703,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
703
703
  output: 128000,
704
704
  },
705
705
  },
706
+ {
707
+ id: 'gpt-5.3-codex-spark',
708
+ label: 'GPT-5.3 Codex Spark',
709
+ modalities: {
710
+ input: ['text', 'image', 'pdf'],
711
+ output: ['text'],
712
+ },
713
+ toolCall: true,
714
+ reasoningText: true,
715
+ attachment: true,
716
+ temperature: false,
717
+ knowledge: '2025-08-31',
718
+ releaseDate: '2026-02-05',
719
+ lastUpdated: '2026-02-05',
720
+ openWeights: false,
721
+ cost: {
722
+ input: 1.75,
723
+ output: 14,
724
+ cacheRead: 0.175,
725
+ },
726
+ limit: {
727
+ context: 128000,
728
+ output: 32000,
729
+ },
730
+ },
706
731
  {
707
732
  id: 'o1',
708
733
  label: 'o1',
@@ -5229,29 +5254,6 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
5229
5254
  output: 100000,
5230
5255
  },
5231
5256
  },
5232
- {
5233
- id: 'openrouter/pony-alpha',
5234
- label: 'Pony Alpha',
5235
- modalities: {
5236
- input: ['text'],
5237
- output: ['text'],
5238
- },
5239
- toolCall: true,
5240
- reasoningText: true,
5241
- attachment: false,
5242
- temperature: true,
5243
- releaseDate: '2026-02-06',
5244
- lastUpdated: '2026-02-06',
5245
- openWeights: false,
5246
- cost: {
5247
- input: 0,
5248
- output: 0,
5249
- },
5250
- limit: {
5251
- context: 200000,
5252
- output: 131000,
5253
- },
5254
- },
5255
5257
  {
5256
5258
  id: 'openrouter/sherlock-dash-alpha',
5257
5259
  label: 'Sherlock Dash Alpha',
@@ -6043,6 +6045,55 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
6043
6045
  output: 8192,
6044
6046
  },
6045
6047
  },
6048
+ {
6049
+ id: 'stepfun/step-3.5-flash',
6050
+ label: 'Step 3.5 Flash',
6051
+ modalities: {
6052
+ input: ['text'],
6053
+ output: ['text'],
6054
+ },
6055
+ toolCall: true,
6056
+ reasoningText: true,
6057
+ attachment: false,
6058
+ temperature: true,
6059
+ knowledge: '2025-01',
6060
+ releaseDate: '2026-01-29',
6061
+ lastUpdated: '2026-01-29',
6062
+ openWeights: true,
6063
+ cost: {
6064
+ input: 0.1,
6065
+ output: 0.3,
6066
+ cacheRead: 0.02,
6067
+ },
6068
+ limit: {
6069
+ context: 256000,
6070
+ output: 256000,
6071
+ },
6072
+ },
6073
+ {
6074
+ id: 'stepfun/step-3.5-flash:free',
6075
+ label: 'Step 3.5 Flash (free)',
6076
+ modalities: {
6077
+ input: ['text'],
6078
+ output: ['text'],
6079
+ },
6080
+ toolCall: true,
6081
+ reasoningText: true,
6082
+ attachment: false,
6083
+ temperature: true,
6084
+ knowledge: '2025-01',
6085
+ releaseDate: '2026-01-29',
6086
+ lastUpdated: '2026-01-29',
6087
+ openWeights: true,
6088
+ cost: {
6089
+ input: 0,
6090
+ output: 0,
6091
+ },
6092
+ limit: {
6093
+ context: 256000,
6094
+ output: 256000,
6095
+ },
6096
+ },
6046
6097
  {
6047
6098
  id: 'thudm/glm-z1-32b:free',
6048
6099
  label: 'GLM Z1 32B (free)',
@@ -6541,6 +6592,30 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
6541
6592
  output: 65535,
6542
6593
  },
6543
6594
  },
6595
+ {
6596
+ id: 'z-ai/glm-5',
6597
+ label: 'GLM-5',
6598
+ modalities: {
6599
+ input: ['text'],
6600
+ output: ['text'],
6601
+ },
6602
+ toolCall: true,
6603
+ reasoningText: true,
6604
+ attachment: false,
6605
+ temperature: true,
6606
+ releaseDate: '2026-02-12',
6607
+ lastUpdated: '2026-02-12',
6608
+ openWeights: true,
6609
+ cost: {
6610
+ input: 1,
6611
+ output: 3.2,
6612
+ cacheRead: 0.2,
6613
+ },
6614
+ limit: {
6615
+ context: 202752,
6616
+ output: 131000,
6617
+ },
6618
+ },
6544
6619
  ],
6545
6620
  label: 'OpenRouter',
6546
6621
  env: ['OPENROUTER_API_KEY'],
@@ -7341,6 +7416,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
7341
7416
  npm: '@ai-sdk/anthropic',
7342
7417
  },
7343
7418
  },
7419
+ {
7420
+ id: 'minimax-m2.5-free',
7421
+ label: 'MiniMax M2.5 Free',
7422
+ modalities: {
7423
+ input: ['text'],
7424
+ output: ['text'],
7425
+ },
7426
+ toolCall: true,
7427
+ reasoningText: true,
7428
+ attachment: false,
7429
+ temperature: true,
7430
+ knowledge: '2025-01',
7431
+ releaseDate: '2026-02-12',
7432
+ lastUpdated: '2026-02-12',
7433
+ openWeights: true,
7434
+ cost: {
7435
+ input: 0,
7436
+ output: 0,
7437
+ cacheRead: 0,
7438
+ },
7439
+ limit: {
7440
+ context: 204800,
7441
+ output: 131072,
7442
+ },
7443
+ },
7344
7444
  {
7345
7445
  id: 'qwen3-coder',
7346
7446
  label: 'Qwen3 Coder',
@@ -7603,6 +7703,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
7603
7703
  output: 131072,
7604
7704
  },
7605
7705
  },
7706
+ {
7707
+ id: 'glm-5',
7708
+ label: 'GLM-5',
7709
+ modalities: {
7710
+ input: ['text'],
7711
+ output: ['text'],
7712
+ },
7713
+ toolCall: true,
7714
+ reasoningText: true,
7715
+ attachment: false,
7716
+ temperature: true,
7717
+ releaseDate: '2026-02-11',
7718
+ lastUpdated: '2026-02-11',
7719
+ openWeights: false,
7720
+ cost: {
7721
+ input: 1,
7722
+ output: 3.2,
7723
+ cacheRead: 0.2,
7724
+ cacheWrite: 0,
7725
+ },
7726
+ limit: {
7727
+ context: 204800,
7728
+ output: 131072,
7729
+ },
7730
+ },
7606
7731
  ],
7607
7732
  label: 'Z.AI',
7608
7733
  env: ['ZHIPU_API_KEY'],
@@ -7817,6 +7942,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
7817
7942
  output: 131072,
7818
7943
  },
7819
7944
  },
7945
+ {
7946
+ id: 'glm-5',
7947
+ label: 'GLM-5',
7948
+ modalities: {
7949
+ input: ['text'],
7950
+ output: ['text'],
7951
+ },
7952
+ toolCall: true,
7953
+ reasoningText: true,
7954
+ attachment: false,
7955
+ temperature: true,
7956
+ releaseDate: '2026-02-11',
7957
+ lastUpdated: '2026-02-11',
7958
+ openWeights: false,
7959
+ cost: {
7960
+ input: 0,
7961
+ output: 0,
7962
+ cacheRead: 0,
7963
+ cacheWrite: 0,
7964
+ },
7965
+ limit: {
7966
+ context: 204800,
7967
+ output: 131072,
7968
+ },
7969
+ },
7820
7970
  ],
7821
7971
  label: 'Z.AI Coding Plan',
7822
7972
  env: ['ZHIPU_API_KEY'],
@@ -8008,7 +8158,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
8008
8158
  },
8009
8159
  limit: {
8010
8160
  context: 128000,
8011
- output: 16000,
8161
+ output: 32000,
8012
8162
  },
8013
8163
  },
8014
8164
  {
@@ -8032,7 +8182,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
8032
8182
  },
8033
8183
  limit: {
8034
8184
  context: 128000,
8035
- output: 16000,
8185
+ output: 32000,
8036
8186
  },
8037
8187
  },
8038
8188
  {
@@ -8128,7 +8278,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
8128
8278
  },
8129
8279
  limit: {
8130
8280
  context: 128000,
8131
- output: 16000,
8281
+ output: 32000,
8132
8282
  },
8133
8283
  },
8134
8284
  {
@@ -8223,7 +8373,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
8223
8373
  output: 0,
8224
8374
  },
8225
8375
  limit: {
8226
- context: 128000,
8376
+ context: 64000,
8227
8377
  output: 16384,
8228
8378
  },
8229
8379
  },
@@ -8320,7 +8470,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
8320
8470
  },
8321
8471
  limit: {
8322
8472
  context: 128000,
8323
- output: 128000,
8473
+ output: 64000,
8324
8474
  },
8325
8475
  },
8326
8476
  {
@@ -8392,7 +8542,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
8392
8542
  },
8393
8543
  limit: {
8394
8544
  context: 128000,
8395
- output: 100000,
8545
+ output: 128000,
8396
8546
  },
8397
8547
  },
8398
8548
  {
@@ -35,6 +35,7 @@ export type {
35
35
  SetuAuth,
36
36
  SetuProviderOptions,
37
37
  SetuPaymentCallbacks,
38
+ SetuBalanceUpdate,
38
39
  SetuBalanceResponse,
39
40
  SolanaUsdcBalanceResponse,
40
41
  } from './setu-client.ts';
@@ -44,6 +44,13 @@ const DEFAULT_RPC_URL = 'https://api.mainnet-beta.solana.com';
44
44
  const DEFAULT_MAX_ATTEMPTS = 3;
45
45
  const DEFAULT_MAX_PAYMENT_ATTEMPTS = 20;
46
46
 
47
+ export type SetuBalanceUpdate = {
48
+ costUsd: number;
49
+ balanceRemaining: number;
50
+ inputTokens?: number;
51
+ outputTokens?: number;
52
+ };
53
+
47
54
  export type SetuPaymentCallbacks = {
48
55
  onPaymentRequired?: (amountUsd: number, currentBalance?: number) => void;
49
56
  onPaymentSigning?: () => void;
@@ -57,6 +64,7 @@ export type SetuPaymentCallbacks = {
57
64
  amountUsd: number;
58
65
  currentBalance: number;
59
66
  }) => Promise<'crypto' | 'fiat' | 'cancel'>;
67
+ onBalanceUpdate?: (update: SetuBalanceUpdate) => void;
60
68
  };
61
69
 
62
70
  export type SetuProviderOptions = {
@@ -197,7 +205,7 @@ export function createSetuFetch(
197
205
  const response = await baseFetch(input, { ...init, body, headers });
198
206
 
199
207
  if (response.status !== 402) {
200
- return response;
208
+ return wrapResponseWithBalanceSniffing(response, callbacks);
201
209
  }
202
210
 
203
211
  const payload = await response.json().catch(() => ({}));
@@ -266,6 +274,70 @@ export function createSetuFetch(
266
274
  };
267
275
  }
268
276
 
277
+ function tryParseSetuComment(
278
+ line: string,
279
+ onBalanceUpdate: (update: SetuBalanceUpdate) => void,
280
+ ) {
281
+ const trimmed = line.replace(/\r$/, '');
282
+ if (!trimmed.startsWith(': setu ')) return;
283
+ try {
284
+ const data = JSON.parse(trimmed.slice(7));
285
+ onBalanceUpdate({
286
+ costUsd: parseFloat(data.cost_usd ?? '0'),
287
+ balanceRemaining: parseFloat(data.balance_remaining ?? '0'),
288
+ inputTokens: data.input_tokens ? Number(data.input_tokens) : undefined,
289
+ outputTokens: data.output_tokens ? Number(data.output_tokens) : undefined,
290
+ });
291
+ } catch {}
292
+ }
293
+
294
+ function wrapResponseWithBalanceSniffing(
295
+ response: Response,
296
+ callbacks: SetuPaymentCallbacks,
297
+ ): Response {
298
+ if (!callbacks.onBalanceUpdate) return response;
299
+
300
+ const balanceHeader = response.headers.get('x-balance-remaining');
301
+ const costHeader = response.headers.get('x-cost-usd');
302
+ if (balanceHeader && costHeader) {
303
+ callbacks.onBalanceUpdate({
304
+ costUsd: parseFloat(costHeader),
305
+ balanceRemaining: parseFloat(balanceHeader),
306
+ });
307
+ return response;
308
+ }
309
+
310
+ if (!response.body) return response;
311
+
312
+ const onBalanceUpdate = callbacks.onBalanceUpdate;
313
+ let partial = '';
314
+ const decoder = new TextDecoder();
315
+ const transform = new TransformStream<Uint8Array, Uint8Array>({
316
+ transform(chunk, controller) {
317
+ controller.enqueue(chunk);
318
+ partial += decoder.decode(chunk, { stream: true });
319
+ let nlIndex = partial.indexOf('\n');
320
+ while (nlIndex !== -1) {
321
+ const line = partial.slice(0, nlIndex);
322
+ partial = partial.slice(nlIndex + 1);
323
+ tryParseSetuComment(line, onBalanceUpdate);
324
+ nlIndex = partial.indexOf('\n');
325
+ }
326
+ },
327
+ flush() {
328
+ if (partial.trim()) {
329
+ tryParseSetuComment(partial, onBalanceUpdate);
330
+ }
331
+ },
332
+ });
333
+
334
+ return new Response(response.body.pipeThrough(transform), {
335
+ status: response.status,
336
+ statusText: response.statusText,
337
+ headers: response.headers,
338
+ });
339
+ }
340
+
269
341
  /**
270
342
  * Create a Setu-backed AI model.
271
343
  *