@antseed/cli 0.1.140 → 0.1.142

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/cli/commands/buyer/sweep.d.ts.map +1 -1
  2. package/dist/cli/commands/buyer/sweep.js +11 -5
  3. package/dist/cli/commands/buyer/sweep.js.map +1 -1
  4. package/dist/cli/commands/payments.d.ts.map +1 -1
  5. package/dist/cli/commands/payments.js +37 -20
  6. package/dist/cli/commands/payments.js.map +1 -1
  7. package/dist/cli/commands/system-proxy/ca-trust-state.d.ts +8 -0
  8. package/dist/cli/commands/system-proxy/ca-trust-state.d.ts.map +1 -0
  9. package/dist/cli/commands/system-proxy/ca-trust-state.js +20 -0
  10. package/dist/cli/commands/system-proxy/ca-trust-state.js.map +1 -0
  11. package/dist/cli/commands/system-proxy/index.d.ts.map +1 -1
  12. package/dist/cli/commands/system-proxy/index.js +2 -0
  13. package/dist/cli/commands/system-proxy/index.js.map +1 -1
  14. package/dist/cli/commands/system-proxy/install-ca.d.ts.map +1 -1
  15. package/dist/cli/commands/system-proxy/install-ca.js +9 -0
  16. package/dist/cli/commands/system-proxy/install-ca.js.map +1 -1
  17. package/dist/cli/commands/system-proxy/start.d.ts.map +1 -1
  18. package/dist/cli/commands/system-proxy/start.js +3 -1
  19. package/dist/cli/commands/system-proxy/start.js.map +1 -1
  20. package/dist/cli/commands/wrapped-tools.d.ts.map +1 -1
  21. package/dist/cli/commands/wrapped-tools.js +7 -0
  22. package/dist/cli/commands/wrapped-tools.js.map +1 -1
  23. package/dist/cli/commands/wrapped-tools.test.js +6 -1
  24. package/dist/cli/commands/wrapped-tools.test.js.map +1 -1
  25. package/dist/proxy/buyer-proxy.d.ts +38 -1
  26. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  27. package/dist/proxy/buyer-proxy.js +340 -23
  28. package/dist/proxy/buyer-proxy.js.map +1 -1
  29. package/dist/proxy/buyer-proxy.test.js +382 -11
  30. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  31. package/dist/proxy/conversation-identity.d.ts +79 -0
  32. package/dist/proxy/conversation-identity.d.ts.map +1 -0
  33. package/dist/proxy/conversation-identity.js +302 -0
  34. package/dist/proxy/conversation-identity.js.map +1 -0
  35. package/dist/proxy/conversation-identity.test.d.ts +2 -0
  36. package/dist/proxy/conversation-identity.test.d.ts.map +1 -0
  37. package/dist/proxy/conversation-identity.test.js +236 -0
  38. package/dist/proxy/conversation-identity.test.js.map +1 -0
  39. package/dist/proxy/conversation-store.d.ts +96 -0
  40. package/dist/proxy/conversation-store.d.ts.map +1 -0
  41. package/dist/proxy/conversation-store.js +242 -0
  42. package/dist/proxy/conversation-store.js.map +1 -0
  43. package/dist/proxy/conversation-store.test.d.ts +2 -0
  44. package/dist/proxy/conversation-store.test.d.ts.map +1 -0
  45. package/dist/proxy/conversation-store.test.js +277 -0
  46. package/dist/proxy/conversation-store.test.js.map +1 -0
  47. package/dist/proxy/count-tokens.d.ts +20 -0
  48. package/dist/proxy/count-tokens.d.ts.map +1 -0
  49. package/dist/proxy/count-tokens.js +75 -0
  50. package/dist/proxy/count-tokens.js.map +1 -0
  51. package/dist/proxy/count-tokens.test.d.ts +2 -0
  52. package/dist/proxy/count-tokens.test.d.ts.map +1 -0
  53. package/dist/proxy/count-tokens.test.js +56 -0
  54. package/dist/proxy/count-tokens.test.js.map +1 -0
  55. package/dist/proxy/request-utils.d.ts +38 -0
  56. package/dist/proxy/request-utils.d.ts.map +1 -1
  57. package/dist/proxy/request-utils.js +84 -8
  58. package/dist/proxy/request-utils.js.map +1 -1
  59. package/dist/proxy/telemetry.d.ts +6 -0
  60. package/dist/proxy/telemetry.d.ts.map +1 -1
  61. package/dist/proxy/telemetry.js +53 -13
  62. package/dist/proxy/telemetry.js.map +1 -1
  63. package/dist/system-proxy/ca-manager.d.ts +67 -0
  64. package/dist/system-proxy/ca-manager.d.ts.map +1 -1
  65. package/dist/system-proxy/ca-manager.js +258 -39
  66. package/dist/system-proxy/ca-manager.js.map +1 -1
  67. package/dist/system-proxy/ca-manager.test.js +50 -1
  68. package/dist/system-proxy/ca-manager.test.js.map +1 -1
  69. package/dist/system-proxy/profiles.d.ts +2 -1
  70. package/dist/system-proxy/profiles.d.ts.map +1 -1
  71. package/dist/system-proxy/profiles.js +55 -4
  72. package/dist/system-proxy/profiles.js.map +1 -1
  73. package/dist/system-proxy/proxy-server.d.ts +18 -1
  74. package/dist/system-proxy/proxy-server.d.ts.map +1 -1
  75. package/dist/system-proxy/proxy-server.js +91 -38
  76. package/dist/system-proxy/proxy-server.js.map +1 -1
  77. package/dist/system-proxy/proxy-server.test.js +142 -13
  78. package/dist/system-proxy/proxy-server.test.js.map +1 -1
  79. package/dist/system-proxy/system-proxy.d.ts +1 -0
  80. package/dist/system-proxy/system-proxy.d.ts.map +1 -1
  81. package/dist/system-proxy/system-proxy.js +57 -15
  82. package/dist/system-proxy/system-proxy.js.map +1 -1
  83. package/dist/system-proxy/types.d.ts +17 -4
  84. package/dist/system-proxy/types.d.ts.map +1 -1
  85. package/package.json +3 -3
@@ -0,0 +1,277 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import test from 'node:test';
6
+ import { ConversationStore, CONVERSATIONS_FILE, conversationId } from './conversation-store.js';
7
+ async function makeDir() {
8
+ return mkdtemp(join(tmpdir(), 'antseed-conv-'));
9
+ }
10
+ test('touch creates a conversation and keeps the original snippet', async () => {
11
+ const dir = await makeDir();
12
+ try {
13
+ const store = new ConversationStore(dir);
14
+ const created = store.touch({ tool: 'codex', sessionKey: 's1', snippet: 'first prompt' });
15
+ assert.equal(created.id, 'codex:s1');
16
+ assert.equal(created.snippet, 'first prompt');
17
+ assert.equal(created.pinnedModel, null);
18
+ const touched = store.touch({ tool: 'codex', sessionKey: 's1', snippet: 'second prompt', lastModel: 'a'.repeat(40) + '@gpt-5.4' });
19
+ assert.equal(touched.snippet, 'first prompt');
20
+ assert.equal(touched.lastModel, 'a'.repeat(40) + '@gpt-5.4');
21
+ assert.ok(touched.lastActiveAt >= created.lastActiveAt);
22
+ await store.flush();
23
+ }
24
+ finally {
25
+ await rm(dir, { recursive: true, force: true });
26
+ }
27
+ });
28
+ test('first resolved model becomes the pin and later touches never replace it', async () => {
29
+ const dir = await makeDir();
30
+ try {
31
+ const store = new ConversationStore(dir);
32
+ const firstModel = 'a'.repeat(40) + '@gpt-5.4';
33
+ const laterModel = 'b'.repeat(40) + '@glm-5';
34
+ // Created without a resolved model (e.g. a title request): no pin yet.
35
+ const created = store.touch({ tool: 'codex', sessionKey: 's1' });
36
+ assert.equal(created.pinnedModel, null);
37
+ // The first request that resolves a model pins the chat to it.
38
+ const pinned = store.touch({ tool: 'codex', sessionKey: 's1', lastModel: firstModel });
39
+ assert.equal(pinned.pinnedModel, firstModel);
40
+ // A later request served by a different route (default changed) keeps
41
+ // the original pin — the default only steers chats that haven't started.
42
+ const touched = store.touch({ tool: 'codex', sessionKey: 's1', lastModel: laterModel });
43
+ assert.equal(touched.pinnedModel, firstModel);
44
+ assert.equal(touched.lastModel, laterModel);
45
+ // A brand-new chat pins to its first model immediately.
46
+ const fresh = store.touch({ tool: 'codex', sessionKey: 's2', lastModel: laterModel });
47
+ assert.equal(fresh.pinnedModel, laterModel);
48
+ await store.flush();
49
+ }
50
+ finally {
51
+ await rm(dir, { recursive: true, force: true });
52
+ }
53
+ });
54
+ test('persists to conversations.json and reloads across instances', async () => {
55
+ const dir = await makeDir();
56
+ try {
57
+ const store = new ConversationStore(dir);
58
+ store.touch({ tool: 'opencode', sessionKey: 'ses_a', snippet: 'hello there' });
59
+ store.setLabel(conversationId('opencode', 'ses_a'), ' My renamed chat ');
60
+ store.setPinnedModel(conversationId('opencode', 'ses_a'), 'b'.repeat(40) + '@glm-5');
61
+ await store.flush();
62
+ const raw = JSON.parse(await readFile(join(dir, CONVERSATIONS_FILE), 'utf8'));
63
+ assert.equal(raw.conversations.length, 1);
64
+ const reloaded = new ConversationStore(dir);
65
+ const record = reloaded.get('opencode:ses_a');
66
+ assert.ok(record);
67
+ assert.equal(record.label, 'My renamed chat');
68
+ assert.equal(record.snippet, 'hello there');
69
+ assert.equal(reloaded.getPinnedModel('opencode', 'ses_a'), 'b'.repeat(40) + '@glm-5');
70
+ }
71
+ finally {
72
+ await rm(dir, { recursive: true, force: true });
73
+ }
74
+ });
75
+ test('label can be cleared and pin can be cleared', async () => {
76
+ const dir = await makeDir();
77
+ try {
78
+ const store = new ConversationStore(dir);
79
+ store.touch({ tool: 'claude-code', sessionKey: 'k1' });
80
+ const id = conversationId('claude-code', 'k1');
81
+ store.setLabel(id, 'named');
82
+ store.setPinnedModel(id, 'c'.repeat(40) + '@claude-opus-4-8');
83
+ assert.equal(store.setLabel(id, null)?.label, null);
84
+ assert.equal(store.setPinnedModel(id, null)?.pinnedModel, null);
85
+ assert.equal(store.setLabel('unknown:id', 'x'), null);
86
+ await store.flush();
87
+ }
88
+ finally {
89
+ await rm(dir, { recursive: true, force: true });
90
+ }
91
+ });
92
+ test('remove deletes the record; a later touch recreates it fresh', async () => {
93
+ const dir = await makeDir();
94
+ try {
95
+ const store = new ConversationStore(dir);
96
+ store.touch({ tool: 'codex', sessionKey: 's1', snippet: 'old snippet' });
97
+ assert.equal(store.remove('codex:s1'), true);
98
+ assert.equal(store.remove('codex:s1'), false);
99
+ assert.equal(store.get('codex:s1'), null);
100
+ const recreated = store.touch({ tool: 'codex', sessionKey: 's1', snippet: 'new snippet' });
101
+ assert.equal(recreated.snippet, 'new snippet');
102
+ await store.flush();
103
+ }
104
+ finally {
105
+ await rm(dir, { recursive: true, force: true });
106
+ }
107
+ });
108
+ test('prunes beyond the LRU cap, keeping the most recently active', async () => {
109
+ const dir = await makeDir();
110
+ try {
111
+ const store = new ConversationStore(dir);
112
+ for (let i = 0; i < 60; i += 1) {
113
+ store.touch({ tool: 'codex', sessionKey: `s${i}` });
114
+ }
115
+ const listed = store.list();
116
+ assert.equal(listed.length, 50);
117
+ // The earliest-touched sessions are the ones evicted.
118
+ assert.equal(store.get('codex:s59') !== null, true);
119
+ await store.flush();
120
+ }
121
+ finally {
122
+ await rm(dir, { recursive: true, force: true });
123
+ }
124
+ });
125
+ test('list returns newest activity first', async () => {
126
+ const dir = await makeDir();
127
+ try {
128
+ const store = new ConversationStore(dir);
129
+ store.touch({ tool: 'codex', sessionKey: 'older' });
130
+ await new Promise((resolve) => setTimeout(resolve, 5));
131
+ store.touch({ tool: 'codex', sessionKey: 'newer' });
132
+ const [first] = store.list();
133
+ assert.equal(first?.sessionKey, 'newer');
134
+ await store.flush();
135
+ }
136
+ finally {
137
+ await rm(dir, { recursive: true, force: true });
138
+ }
139
+ });
140
+ test('survives a corrupted file by starting clean', async () => {
141
+ const dir = await makeDir();
142
+ try {
143
+ const { writeFile } = await import('node:fs/promises');
144
+ await writeFile(join(dir, CONVERSATIONS_FILE), 'not json at all', 'utf8');
145
+ const store = new ConversationStore(dir);
146
+ assert.deepEqual(store.list(), []);
147
+ store.touch({ tool: 'codex', sessionKey: 's1' });
148
+ await store.flush();
149
+ const reloaded = new ConversationStore(dir);
150
+ assert.equal(reloaded.list().length, 1);
151
+ }
152
+ finally {
153
+ await rm(dir, { recursive: true, force: true });
154
+ }
155
+ });
156
+ test('reload re-cleans snippets persisted by older extraction rules', async () => {
157
+ const dir = await makeDir();
158
+ try {
159
+ const { writeFile } = await import('node:fs/promises');
160
+ await writeFile(join(dir, CONVERSATIONS_FILE), JSON.stringify({
161
+ conversations: [
162
+ {
163
+ tool: 'claude-code', sessionKey: 'old1',
164
+ snippet: '<session> try to understand who reviews are from </session> and more text',
165
+ createdAt: Date.now(), lastActiveAt: Date.now(),
166
+ },
167
+ {
168
+ tool: 'opencode', sessionKey: 'old2',
169
+ snippet: 'Generate a title for this conversation:',
170
+ createdAt: Date.now(), lastActiveAt: Date.now(),
171
+ },
172
+ ],
173
+ }), 'utf8');
174
+ const store = new ConversationStore(dir);
175
+ assert.equal(store.get('claude-code:old1')?.snippet, 'try to understand who reviews are from and more text');
176
+ // Old title-request snippets are dropped so the next real turn names the chat.
177
+ assert.equal(store.get('opencode:old2')?.snippet, '');
178
+ await store.flush();
179
+ }
180
+ finally {
181
+ await rm(dir, { recursive: true, force: true });
182
+ }
183
+ });
184
+ test('addSpend accumulates cost and counts requests only when told to', async () => {
185
+ const dir = await makeDir();
186
+ try {
187
+ const store = new ConversationStore(dir);
188
+ const created = store.touch({ tool: 'codex', sessionKey: 's1', snippet: 'hello' });
189
+ assert.equal(created.spentUsdc, '0');
190
+ assert.equal(created.requestCount, 0);
191
+ store.addSpend(created.id, { amountUsdc: '12000', inputTokens: '900', cachedInputTokens: '600', outputTokens: '120' });
192
+ // A second delta for the same request: cost adds up, the request does not.
193
+ store.addSpend(created.id, { amountUsdc: '3000', inputTokens: '0', cachedInputTokens: '0', outputTokens: '0' }, false);
194
+ store.addSpend(created.id, { amountUsdc: '5000', inputTokens: '100', cachedInputTokens: '25', outputTokens: '40' });
195
+ const row = store.get(created.id);
196
+ assert.equal(row?.spentUsdc, '20000');
197
+ assert.equal(row?.inputTokens, '1000');
198
+ assert.equal(row?.cachedInputTokens, '625');
199
+ assert.equal(row?.outputTokens, '160');
200
+ assert.equal(row?.requestCount, 2);
201
+ await store.flush();
202
+ }
203
+ finally {
204
+ await rm(dir, { recursive: true, force: true });
205
+ }
206
+ });
207
+ test('addSpend ignores unknown ids, empty deltas and malformed amounts', async () => {
208
+ const dir = await makeDir();
209
+ try {
210
+ const store = new ConversationStore(dir);
211
+ const created = store.touch({ tool: 'codex', sessionKey: 's1' });
212
+ // A chat pruned mid-request must not come back as a spend-only row.
213
+ store.addSpend('codex:gone', { amountUsdc: '5000', inputTokens: '0', cachedInputTokens: '0', outputTokens: '0' });
214
+ assert.equal(store.get('codex:gone'), null);
215
+ store.addSpend(created.id, { amountUsdc: '0', inputTokens: '0', cachedInputTokens: '0', outputTokens: '0' });
216
+ store.addSpend(created.id, { amountUsdc: 'not-a-number', inputTokens: '0', cachedInputTokens: '0', outputTokens: '0' });
217
+ const row = store.get(created.id);
218
+ assert.equal(row?.spentUsdc, '0');
219
+ assert.equal(row?.requestCount, 0);
220
+ await store.flush();
221
+ }
222
+ finally {
223
+ await rm(dir, { recursive: true, force: true });
224
+ }
225
+ });
226
+ test('spend counters survive a reload and heal from corrupt values', async () => {
227
+ const dir = await makeDir();
228
+ try {
229
+ const store = new ConversationStore(dir);
230
+ const created = store.touch({ tool: 'codex', sessionKey: 's1' });
231
+ store.addSpend(created.id, { amountUsdc: '7500', inputTokens: '10', cachedInputTokens: '4', outputTokens: '5' });
232
+ // addSpend defers persistence to the next touch, mirroring live traffic.
233
+ store.touch({ tool: 'codex', sessionKey: 's1' });
234
+ await store.flush();
235
+ const reloaded = new ConversationStore(dir);
236
+ assert.equal(reloaded.get(created.id)?.spentUsdc, '7500');
237
+ assert.equal(reloaded.get(created.id)?.cachedInputTokens, '4');
238
+ assert.equal(reloaded.get(created.id)?.requestCount, 1);
239
+ const { writeFile } = await import('node:fs/promises');
240
+ await writeFile(join(dir, CONVERSATIONS_FILE), JSON.stringify({
241
+ conversations: [{
242
+ tool: 'codex', sessionKey: 's2', spentUsdc: 'garbage', cachedInputTokens: null, requestCount: -3,
243
+ createdAt: Date.now(), lastActiveAt: Date.now(),
244
+ }],
245
+ }), 'utf8');
246
+ const healed = new ConversationStore(dir);
247
+ assert.equal(healed.get(conversationId('codex', 's2'))?.spentUsdc, '0');
248
+ assert.equal(healed.get(conversationId('codex', 's2'))?.cachedInputTokens, '0');
249
+ assert.equal(healed.get(conversationId('codex', 's2'))?.requestCount, 0);
250
+ await healed.flush();
251
+ }
252
+ finally {
253
+ await rm(dir, { recursive: true, force: true });
254
+ }
255
+ });
256
+ test('cached input is clamped to the input it is a subset of', async () => {
257
+ const dir = await makeDir();
258
+ try {
259
+ const store = new ConversationStore(dir);
260
+ const created = store.touch({ tool: 'codex', sessionKey: 's1' });
261
+ // A seller reporting more cached than total input is malformed — the
262
+ // subset must never exceed the whole it is measured against.
263
+ store.addSpend(created.id, { amountUsdc: '1000', inputTokens: '50', cachedInputTokens: '900', outputTokens: '10' });
264
+ const row = store.get(created.id);
265
+ assert.equal(row?.inputTokens, '50');
266
+ assert.equal(row?.cachedInputTokens, '50');
267
+ // Fresh input stays derivable as inputTokens - cachedInputTokens.
268
+ store.addSpend(created.id, { amountUsdc: '1000', inputTokens: '200', cachedInputTokens: '150', outputTokens: '10' });
269
+ const after = store.get(created.id);
270
+ assert.equal(BigInt(after.inputTokens) - BigInt(after.cachedInputTokens), 50n);
271
+ await store.flush();
272
+ }
273
+ finally {
274
+ await rm(dir, { recursive: true, force: true });
275
+ }
276
+ });
277
+ //# sourceMappingURL=conversation-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-store.test.js","sourceRoot":"","sources":["../../src/proxy/conversation-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Anthropic clients size a conversation against the context window by calling
3
+ * `POST /v1/messages/count_tokens` before a turn. It looks like a completion
4
+ * path but is not one: it must not reach a seller. Routed as a completion it
5
+ * bills a full inference, spends seconds of model time, and answers with a
6
+ * message body where the client expects `{ "input_tokens": n }` — so the tool
7
+ * stalls on every turn that has context to measure.
8
+ *
9
+ * The buyer answers it locally instead, from the same tokenx estimate the
10
+ * pricing layer uses (~95-98% of the real count).
11
+ */
12
+ /** Anthropic's token-count endpoint, on any API version prefix. */
13
+ export declare function isCountTokensPath(path: string): boolean;
14
+ /**
15
+ * Estimate the prompt tokens an Anthropic `count_tokens` request would bill:
16
+ * system prompt, tool definitions and the full message history. Structural
17
+ * JSON is skipped — only the text a model actually reads is counted.
18
+ */
19
+ export declare function estimateAnthropicPromptTokens(body: Uint8Array): number;
20
+ //# sourceMappingURL=count-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"count-tokens.d.ts","sourceRoot":"","sources":["../../src/proxy/count-tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAkBtE"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Anthropic clients size a conversation against the context window by calling
3
+ * `POST /v1/messages/count_tokens` before a turn. It looks like a completion
4
+ * path but is not one: it must not reach a seller. Routed as a completion it
5
+ * bills a full inference, spends seconds of model time, and answers with a
6
+ * message body where the client expects `{ "input_tokens": n }` — so the tool
7
+ * stalls on every turn that has context to measure.
8
+ *
9
+ * The buyer answers it locally instead, from the same tokenx estimate the
10
+ * pricing layer uses (~95-98% of the real count).
11
+ */
12
+ import { estimateTokensFromText } from '@antseed/node';
13
+ import { parseJsonObject } from '@antseed/api-adapter';
14
+ /** Anthropic's token-count endpoint, on any API version prefix. */
15
+ export function isCountTokensPath(path) {
16
+ const clean = (path.split('?')[0] ?? '').replace(/\/+$/, '');
17
+ return clean.endsWith('/messages/count_tokens');
18
+ }
19
+ /**
20
+ * Estimate the prompt tokens an Anthropic `count_tokens` request would bill:
21
+ * system prompt, tool definitions and the full message history. Structural
22
+ * JSON is skipped — only the text a model actually reads is counted.
23
+ */
24
+ export function estimateAnthropicPromptTokens(body) {
25
+ const parsed = parseJsonObject(body);
26
+ if (!parsed)
27
+ return 0;
28
+ const parts = [];
29
+ collectText(parsed['system'], parts);
30
+ collectText(parsed['messages'], parts);
31
+ for (const tool of asArray(parsed['tools'])) {
32
+ const record = asRecord(tool);
33
+ if (!record)
34
+ continue;
35
+ collectText(record['name'], parts);
36
+ collectText(record['description'], parts);
37
+ // Schemas are read by the model verbatim, punctuation included.
38
+ if (record['input_schema'] !== undefined)
39
+ parts.push(JSON.stringify(record['input_schema']));
40
+ }
41
+ const text = parts.join('\n');
42
+ return text.length > 0 ? estimateTokensFromText(text) : 0;
43
+ }
44
+ /** Walk any Anthropic content shape (string, block, array of blocks) for text. */
45
+ function collectText(value, out) {
46
+ if (typeof value === 'string') {
47
+ if (value.length > 0)
48
+ out.push(value);
49
+ return;
50
+ }
51
+ for (const item of asArray(value)) {
52
+ collectText(item, out);
53
+ }
54
+ const record = asRecord(value);
55
+ if (!record)
56
+ return;
57
+ for (const key of ['text', 'content', 'thinking']) {
58
+ if (record[key] !== undefined)
59
+ collectText(record[key], out);
60
+ }
61
+ // tool_use arguments and tool_result payloads are prompt text too.
62
+ if (record['input'] !== undefined)
63
+ out.push(JSON.stringify(record['input']));
64
+ if (typeof record['name'] === 'string' && record['type'] === 'tool_use')
65
+ out.push(record['name']);
66
+ }
67
+ function asArray(value) {
68
+ return Array.isArray(value) ? value : [];
69
+ }
70
+ function asRecord(value) {
71
+ return value && typeof value === 'object' && !Array.isArray(value)
72
+ ? value
73
+ : null;
74
+ }
75
+ //# sourceMappingURL=count-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"count-tokens.js","sourceRoot":"","sources":["../../src/proxy/count-tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAEtD,mEAAmE;AACnE,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC5D,OAAO,KAAK,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAA;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAAC,IAAgB;IAC5D,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACpC,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAA;IAErB,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,WAAW,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAA;IACpC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC,CAAA;IACtC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC7B,IAAI,CAAC,MAAM;YAAE,SAAQ;QACrB,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAA;QAClC,WAAW,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAA;QACzC,gEAAgE;QAChE,IAAI,MAAM,CAAC,cAAc,CAAC,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAA;IAC9F,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7B,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC3D,CAAC;AAED,kFAAkF;AAClF,SAAS,WAAW,CAAC,KAAc,EAAE,GAAa;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrC,OAAM;IACR,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACxB,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC9B,IAAI,CAAC,MAAM;QAAE,OAAM;IACnB,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAU,EAAE,CAAC;QAC3D,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,CAAA;IAC9D,CAAC;IACD,mEAAmE;IACnE,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAC5E,IAAI,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,UAAU;QAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;AACnG,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AAC1C,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAChE,CAAC,CAAC,KAAgC;QAClC,CAAC,CAAC,IAAI,CAAA;AACV,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=count-tokens.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"count-tokens.test.d.ts","sourceRoot":"","sources":["../../src/proxy/count-tokens.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,56 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+ import { estimateAnthropicPromptTokens, isCountTokensPath } from './count-tokens.js';
4
+ const encode = (value) => new TextEncoder().encode(JSON.stringify(value));
5
+ test('isCountTokensPath matches only the token-count endpoint', () => {
6
+ assert.equal(isCountTokensPath('/v1/messages/count_tokens'), true);
7
+ assert.equal(isCountTokensPath('/v1/messages/count_tokens?beta=true'), true);
8
+ assert.equal(isCountTokensPath('/v1/messages/count_tokens/'), true);
9
+ assert.equal(isCountTokensPath('/v1/messages'), false);
10
+ assert.equal(isCountTokensPath('/v1/messages/batches'), false);
11
+ assert.equal(isCountTokensPath('/v1/chat/completions'), false);
12
+ });
13
+ test('estimate counts system, tools and the whole message history', () => {
14
+ const words = 'sentinel '.repeat(200).trim();
15
+ const systemOnly = estimateAnthropicPromptTokens(encode({
16
+ model: 'claude-sonnet-4-5',
17
+ system: words,
18
+ messages: [{ role: 'user', content: 'hi' }],
19
+ }));
20
+ const withHistory = estimateAnthropicPromptTokens(encode({
21
+ model: 'claude-sonnet-4-5',
22
+ system: words,
23
+ messages: [
24
+ { role: 'user', content: 'hi' },
25
+ { role: 'assistant', content: [{ type: 'text', text: words }] },
26
+ { role: 'user', content: 'and now?' },
27
+ ],
28
+ }));
29
+ const withTools = estimateAnthropicPromptTokens(encode({
30
+ model: 'claude-sonnet-4-5',
31
+ system: words,
32
+ tools: [{ name: 'Read', description: words, input_schema: { type: 'object' } }],
33
+ messages: [{ role: 'user', content: 'hi' }],
34
+ }));
35
+ assert.ok(systemOnly > 100, `expected a real count, got ${systemOnly}`);
36
+ assert.ok(withHistory > systemOnly * 1.8, `history must be counted: ${withHistory} vs ${systemOnly}`);
37
+ assert.ok(withTools > systemOnly * 1.8, `tools must be counted: ${withTools} vs ${systemOnly}`);
38
+ });
39
+ test('estimate reads array system blocks and tool traffic', () => {
40
+ const tokens = estimateAnthropicPromptTokens(encode({
41
+ system: [
42
+ { type: 'text', text: 'You are a CLI assistant.' },
43
+ { type: 'text', text: 'Policy text. '.repeat(50), cache_control: { type: 'ephemeral' } },
44
+ ],
45
+ messages: [
46
+ { role: 'assistant', content: [{ type: 'tool_use', id: 't1', name: 'Read', input: { path: 'package.json' } }] },
47
+ { role: 'user', content: [{ type: 'tool_result', tool_use_id: 't1', content: 'file contents here' }] },
48
+ ],
49
+ }));
50
+ assert.ok(tokens > 50, `expected blocks to be counted, got ${tokens}`);
51
+ });
52
+ test('estimate is zero for a body with no prompt text', () => {
53
+ assert.equal(estimateAnthropicPromptTokens(encode({ model: 'claude-sonnet-4-5' })), 0);
54
+ assert.equal(estimateAnthropicPromptTokens(new TextEncoder().encode('not json')), 0);
55
+ });
56
+ //# sourceMappingURL=count-tokens.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"count-tokens.test.js","sourceRoot":"","sources":["../../src/proxy/count-tokens.test.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,44 @@ export declare function summarizeRequestShape(request: SerializedHttpRequest): s
11
11
  export declare function summarizeErrorResponse(response: SerializedHttpResponse): string;
12
12
  export declare function requestWantsStreaming(headers: Record<string, string>, body: Uint8Array): boolean;
13
13
  export declare function isLoopbackPeer(peer: PeerInfo): boolean;
14
+ /**
15
+ * Model alias that tool configs can carry instead of a concrete
16
+ * `<peerId>@<service>` key. The buyer substitutes the session's default
17
+ * routed model at request time, so the route selected in the desktop
18
+ * (floating pill / VPR) applies to already-running tool sessions without
19
+ * rewriting their configs.
20
+ */
21
+ export declare const ROUTED_MODEL_ALIAS = "antseed";
22
+ export declare function substituteRoutedModelAlias(body: Uint8Array, headers: Record<string, string>, defaultRoutedModel: string | null): {
23
+ body: Uint8Array;
24
+ headers: Record<string, string>;
25
+ aliasRequested: boolean;
26
+ substituted: boolean;
27
+ };
28
+ /**
29
+ * Marker header the system proxy stamps on requests whose `model` field IT
30
+ * rewrote from the connect-time route — intercepted tools (Claude Code
31
+ * talking to api.anthropic.com through the MITM proxy) send upstream model
32
+ * names, not an AntSeed route, so the model on such requests is AntSeed
33
+ * plumbing rather than a client choice. The buyer treats those models like
34
+ * the routed-model alias for per-chat routing: a chat's pinned model
35
+ * overrides the proxy-resolved route. Internal — stripped before dispatch.
36
+ */
37
+ export declare const SYSTEM_ROUTED_MODEL_HEADER = "x-antseed-system-routed";
38
+ /** Internal profile marker stamped by the system proxy so intercepted app
39
+ traffic is attributed to the Connected App profile that captured it,
40
+ rather than to a generic SDK User-Agent. Stripped before dispatch. */
41
+ export declare const SYSTEM_PROXY_SOURCE_HEADER = "x-antseed-system-proxy-source";
42
+ /**
43
+ * Replace a proxy-assigned model with the chat's pinned route. Only used for
44
+ * requests carrying SYSTEM_ROUTED_MODEL_HEADER — client-chosen models are
45
+ * never overridden.
46
+ */
47
+ export declare function overrideRoutedModelInBody(body: Uint8Array, headers: Record<string, string>, pinnedModel: string): {
48
+ body: Uint8Array;
49
+ headers: Record<string, string>;
50
+ overridden: boolean;
51
+ };
14
52
  export declare function rewritePeerPinnedServiceInBody(body: Uint8Array, headers: Record<string, string>): {
15
53
  body: Uint8Array;
16
54
  headers: Record<string, string>;
@@ -1 +1 @@
1
- {"version":3,"file":"request-utils.d.ts","sourceRoot":"","sources":["../../src/proxy/request-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,QAAQ,EAAE,KAAK,qBAAqB,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAW3H,wBAAgB,KAAK,IAAI,OAAO,CAE/B;AAED,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE5C;AAMD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI5D;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAYhG;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,CAarF;AAiFD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CA2C5E;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAqC/E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAWhG;AAsBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAMtD;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAsCpF"}
1
+ {"version":3,"file":"request-utils.d.ts","sourceRoot":"","sources":["../../src/proxy/request-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,QAAQ,EAAE,KAAK,qBAAqB,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAW3H,wBAAgB,KAAK,IAAI,OAAO,CAE/B;AAED,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAE5C;AAMD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI5D;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAYhG;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,CAarF;AAiFD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CA2C5E;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAqC/E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAWhG;AAsBD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAMtD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,YAAY,CAAA;AAE3C,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,kBAAkB,EAAE,MAAM,GAAG,IAAI,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAyBtG;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,4BAA4B,CAAA;AAEnE;;yEAEyE;AACzE,eAAO,MAAM,0BAA0B,kCAAkC,CAAA;AAEzE;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,WAAW,EAAE,MAAM,GAClB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAkB5E;AAgBD,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAgCpF"}
@@ -221,6 +221,89 @@ export function isLoopbackPeer(peer) {
221
221
  const host = extractHostFromAddress(peer.publicAddress);
222
222
  return isLoopbackHost(host);
223
223
  }
224
+ /**
225
+ * Model alias that tool configs can carry instead of a concrete
226
+ * `<peerId>@<service>` key. The buyer substitutes the session's default
227
+ * routed model at request time, so the route selected in the desktop
228
+ * (floating pill / VPR) applies to already-running tool sessions without
229
+ * rewriting their configs.
230
+ */
231
+ export const ROUTED_MODEL_ALIAS = 'antseed';
232
+ export function substituteRoutedModelAlias(body, headers, defaultRoutedModel) {
233
+ if (!getHeader(headers, 'content-type').toLowerCase().includes('application/json') || body.length === 0) {
234
+ return { body, headers, aliasRequested: false, substituted: false };
235
+ }
236
+ const obj = parseJsonObject(body);
237
+ if (!obj) {
238
+ return { body, headers, aliasRequested: false, substituted: false };
239
+ }
240
+ const rawModel = typeof obj['model'] === 'string' ? obj['model'].trim() : '';
241
+ const rawService = typeof obj['service'] === 'string' ? obj['service'].trim() : '';
242
+ const modelIsAlias = rawModel.toLowerCase() === ROUTED_MODEL_ALIAS;
243
+ const serviceIsAlias = rawService.toLowerCase() === ROUTED_MODEL_ALIAS;
244
+ if (!modelIsAlias && !serviceIsAlias) {
245
+ return { body, headers, aliasRequested: false, substituted: false };
246
+ }
247
+ const target = defaultRoutedModel?.trim();
248
+ if (!target) {
249
+ return { body, headers, aliasRequested: true, substituted: false };
250
+ }
251
+ if (modelIsAlias)
252
+ obj['model'] = target;
253
+ if (serviceIsAlias)
254
+ obj['service'] = target;
255
+ const { body: rewritten, headers: updatedHeaders } = encodeRewrittenJsonBody(obj, headers);
256
+ return { body: rewritten, headers: updatedHeaders, aliasRequested: true, substituted: true };
257
+ }
258
+ /**
259
+ * Marker header the system proxy stamps on requests whose `model` field IT
260
+ * rewrote from the connect-time route — intercepted tools (Claude Code
261
+ * talking to api.anthropic.com through the MITM proxy) send upstream model
262
+ * names, not an AntSeed route, so the model on such requests is AntSeed
263
+ * plumbing rather than a client choice. The buyer treats those models like
264
+ * the routed-model alias for per-chat routing: a chat's pinned model
265
+ * overrides the proxy-resolved route. Internal — stripped before dispatch.
266
+ */
267
+ export const SYSTEM_ROUTED_MODEL_HEADER = 'x-antseed-system-routed';
268
+ /** Internal profile marker stamped by the system proxy so intercepted app
269
+ traffic is attributed to the Connected App profile that captured it,
270
+ rather than to a generic SDK User-Agent. Stripped before dispatch. */
271
+ export const SYSTEM_PROXY_SOURCE_HEADER = 'x-antseed-system-proxy-source';
272
+ /**
273
+ * Replace a proxy-assigned model with the chat's pinned route. Only used for
274
+ * requests carrying SYSTEM_ROUTED_MODEL_HEADER — client-chosen models are
275
+ * never overridden.
276
+ */
277
+ export function overrideRoutedModelInBody(body, headers, pinnedModel) {
278
+ if (!getHeader(headers, 'content-type').toLowerCase().includes('application/json') || body.length === 0) {
279
+ return { body, headers, overridden: false };
280
+ }
281
+ const obj = parseJsonObject(body);
282
+ if (!obj) {
283
+ return { body, headers, overridden: false };
284
+ }
285
+ const rawModel = typeof obj['model'] === 'string' ? obj['model'].trim() : '';
286
+ if (!rawModel || rawModel === pinnedModel) {
287
+ return { body, headers, overridden: false };
288
+ }
289
+ obj['model'] = pinnedModel;
290
+ if (typeof obj['service'] === 'string' && obj['service'].trim() === rawModel) {
291
+ obj['service'] = pinnedModel;
292
+ }
293
+ const { body: rewritten, headers: updatedHeaders } = encodeRewrittenJsonBody(obj, headers);
294
+ return { body: rewritten, headers: updatedHeaders, overridden: true };
295
+ }
296
+ function encodeRewrittenJsonBody(obj, headers) {
297
+ const rewritten = new TextEncoder().encode(JSON.stringify(obj));
298
+ const updatedHeaders = { ...headers };
299
+ if ('content-length' in updatedHeaders) {
300
+ updatedHeaders['content-length'] = String(rewritten.length);
301
+ }
302
+ else if ('Content-Length' in updatedHeaders) {
303
+ updatedHeaders['Content-Length'] = String(rewritten.length);
304
+ }
305
+ return { body: rewritten, headers: updatedHeaders };
306
+ }
224
307
  export function rewritePeerPinnedServiceInBody(body, headers) {
225
308
  if (!getHeader(headers, 'content-type').toLowerCase().includes('application/json') || body.length === 0) {
226
309
  return { body, headers, pinnedPeerId: null };
@@ -249,14 +332,7 @@ export function rewritePeerPinnedServiceInBody(body, headers) {
249
332
  obj['model'] = parsedService.service;
250
333
  }
251
334
  }
252
- const rewritten = new TextEncoder().encode(JSON.stringify(obj));
253
- const updatedHeaders = { ...headers };
254
- if ('content-length' in updatedHeaders) {
255
- updatedHeaders['content-length'] = String(rewritten.length);
256
- }
257
- else if ('Content-Length' in updatedHeaders) {
258
- updatedHeaders['Content-Length'] = String(rewritten.length);
259
- }
335
+ const { body: rewritten, headers: updatedHeaders } = encodeRewrittenJsonBody(obj, headers);
260
336
  return { body: rewritten, headers: updatedHeaders, pinnedPeerId: parsed.peerId };
261
337
  }
262
338
  //# sourceMappingURL=request-utils.js.map