@antseed/cli 0.1.141 → 0.1.143
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/dist/cli/commands/buyer/sweep.d.ts.map +1 -1
- package/dist/cli/commands/buyer/sweep.js +11 -5
- package/dist/cli/commands/buyer/sweep.js.map +1 -1
- package/dist/cli/commands/payments.d.ts.map +1 -1
- package/dist/cli/commands/payments.js +37 -20
- package/dist/cli/commands/payments.js.map +1 -1
- package/dist/cli/commands/system-proxy/ca-trust-state.d.ts +8 -0
- package/dist/cli/commands/system-proxy/ca-trust-state.d.ts.map +1 -0
- package/dist/cli/commands/system-proxy/ca-trust-state.js +20 -0
- package/dist/cli/commands/system-proxy/ca-trust-state.js.map +1 -0
- package/dist/cli/commands/system-proxy/index.d.ts.map +1 -1
- package/dist/cli/commands/system-proxy/index.js +2 -0
- package/dist/cli/commands/system-proxy/index.js.map +1 -1
- package/dist/cli/commands/system-proxy/install-ca.d.ts.map +1 -1
- package/dist/cli/commands/system-proxy/install-ca.js +9 -0
- package/dist/cli/commands/system-proxy/install-ca.js.map +1 -1
- package/dist/cli/commands/system-proxy/start.d.ts.map +1 -1
- package/dist/cli/commands/system-proxy/start.js +3 -1
- package/dist/cli/commands/system-proxy/start.js.map +1 -1
- package/dist/cli/commands/wrapped-tools.d.ts.map +1 -1
- package/dist/cli/commands/wrapped-tools.js +7 -0
- package/dist/cli/commands/wrapped-tools.js.map +1 -1
- package/dist/cli/commands/wrapped-tools.test.js +6 -1
- package/dist/cli/commands/wrapped-tools.test.js.map +1 -1
- package/dist/proxy/buyer-proxy.d.ts +38 -1
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +340 -23
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +382 -11
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/conversation-identity.d.ts +79 -0
- package/dist/proxy/conversation-identity.d.ts.map +1 -0
- package/dist/proxy/conversation-identity.js +302 -0
- package/dist/proxy/conversation-identity.js.map +1 -0
- package/dist/proxy/conversation-identity.test.d.ts +2 -0
- package/dist/proxy/conversation-identity.test.d.ts.map +1 -0
- package/dist/proxy/conversation-identity.test.js +236 -0
- package/dist/proxy/conversation-identity.test.js.map +1 -0
- package/dist/proxy/conversation-store.d.ts +96 -0
- package/dist/proxy/conversation-store.d.ts.map +1 -0
- package/dist/proxy/conversation-store.js +242 -0
- package/dist/proxy/conversation-store.js.map +1 -0
- package/dist/proxy/conversation-store.test.d.ts +2 -0
- package/dist/proxy/conversation-store.test.d.ts.map +1 -0
- package/dist/proxy/conversation-store.test.js +277 -0
- package/dist/proxy/conversation-store.test.js.map +1 -0
- package/dist/proxy/count-tokens.d.ts +20 -0
- package/dist/proxy/count-tokens.d.ts.map +1 -0
- package/dist/proxy/count-tokens.js +75 -0
- package/dist/proxy/count-tokens.js.map +1 -0
- package/dist/proxy/count-tokens.test.d.ts +2 -0
- package/dist/proxy/count-tokens.test.d.ts.map +1 -0
- package/dist/proxy/count-tokens.test.js +56 -0
- package/dist/proxy/count-tokens.test.js.map +1 -0
- package/dist/proxy/request-utils.d.ts +38 -0
- package/dist/proxy/request-utils.d.ts.map +1 -1
- package/dist/proxy/request-utils.js +84 -8
- package/dist/proxy/request-utils.js.map +1 -1
- package/dist/proxy/telemetry.d.ts +6 -0
- package/dist/proxy/telemetry.d.ts.map +1 -1
- package/dist/proxy/telemetry.js +53 -13
- package/dist/proxy/telemetry.js.map +1 -1
- package/dist/system-proxy/ca-manager.d.ts +67 -0
- package/dist/system-proxy/ca-manager.d.ts.map +1 -1
- package/dist/system-proxy/ca-manager.js +258 -39
- package/dist/system-proxy/ca-manager.js.map +1 -1
- package/dist/system-proxy/ca-manager.test.js +50 -1
- package/dist/system-proxy/ca-manager.test.js.map +1 -1
- package/dist/system-proxy/profiles.d.ts +2 -1
- package/dist/system-proxy/profiles.d.ts.map +1 -1
- package/dist/system-proxy/profiles.js +55 -4
- package/dist/system-proxy/profiles.js.map +1 -1
- package/dist/system-proxy/proxy-server.d.ts +18 -1
- package/dist/system-proxy/proxy-server.d.ts.map +1 -1
- package/dist/system-proxy/proxy-server.js +91 -38
- package/dist/system-proxy/proxy-server.js.map +1 -1
- package/dist/system-proxy/proxy-server.test.js +142 -13
- package/dist/system-proxy/proxy-server.test.js.map +1 -1
- package/dist/system-proxy/system-proxy.d.ts +1 -0
- package/dist/system-proxy/system-proxy.d.ts.map +1 -1
- package/dist/system-proxy/system-proxy.js +57 -15
- package/dist/system-proxy/system-proxy.js.map +1 -1
- package/dist/system-proxy/types.d.ts +17 -4
- package/dist/system-proxy/types.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
2
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { Readable } from 'node:stream';
|
|
6
6
|
import test from 'node:test';
|
|
7
7
|
import { CONNECTION_CAPABILITY_RELAYS_SWEEPS_V1 } from '@antseed/node';
|
|
8
8
|
import { DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS } from '../config/defaults.js';
|
|
9
|
-
import { BuyerProxy, isModelNotFoundResponse, makeVerifierReach, parsePeerPinnedService, parsePersistedPeers, rewritePeerPinnedServiceInBody, selectCandidatePeersForRouting, } from './buyer-proxy.js';
|
|
9
|
+
import { BuyerProxy, isModelNotFoundResponse, makeVerifierReach, parsePeerPinnedService, parsePersistedPeers, rewritePeerPinnedServiceInBody, selectCandidatePeersForRouting, substituteRoutedModelAlias, } from './buyer-proxy.js';
|
|
10
|
+
import { overrideRoutedModelInBody, SYSTEM_ROUTED_MODEL_HEADER } from './request-utils.js';
|
|
10
11
|
function makePeer(seed, providers) {
|
|
11
12
|
const repeated = (seed.repeat(40) + 'a'.repeat(40)).slice(0, 40);
|
|
12
13
|
return {
|
|
@@ -138,7 +139,7 @@ test('selectCandidatePeersForRouting keeps all peers when no protocol or provide
|
|
|
138
139
|
assert.deepEqual(result.candidatePeers.map((peer) => peer.peerId), peers.map((peer) => peer.peerId));
|
|
139
140
|
assert.equal(result.routePlanByPeerId.size, 0);
|
|
140
141
|
});
|
|
141
|
-
test('sweep control endpoint validates and
|
|
142
|
+
test('sweep control endpoint validates and dispatches sequentially via the running node', async () => {
|
|
142
143
|
const validSweep = {
|
|
143
144
|
version: 1,
|
|
144
145
|
evmChainId: 31337,
|
|
@@ -150,7 +151,7 @@ test('sweep control endpoint validates and broadcasts via the running node', asy
|
|
|
150
151
|
nonce: '0x' + 'aa'.repeat(32),
|
|
151
152
|
sig3009: '0x' + 'ab'.repeat(65),
|
|
152
153
|
};
|
|
153
|
-
const
|
|
154
|
+
const dispatches = [];
|
|
154
155
|
const listeners = new Map();
|
|
155
156
|
const proxy = new BuyerProxy({
|
|
156
157
|
port: 0,
|
|
@@ -158,23 +159,23 @@ test('sweep control endpoint validates and broadcasts via the running node', asy
|
|
|
158
159
|
node: {
|
|
159
160
|
router: null,
|
|
160
161
|
on: (event, listener) => listeners.set(event, listener),
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return 3;
|
|
162
|
+
dispatchSweepRequest: async (payload) => {
|
|
163
|
+
dispatches.push(payload);
|
|
164
|
+
return { offered: 3, accepted: true };
|
|
164
165
|
},
|
|
165
166
|
},
|
|
166
167
|
});
|
|
167
168
|
const res = await invokeProxy(proxy, makeProxyRequest({ path: '/_antseed/sweep', body: validSweep }));
|
|
168
169
|
assert.equal(res.statusCode, 200);
|
|
169
|
-
assert.deepEqual(JSON.parse(res.body), { ok: true, sent: 3 });
|
|
170
|
-
assert.equal(
|
|
171
|
-
// Malformed payloads are rejected by the wire codec, not
|
|
170
|
+
assert.deepEqual(JSON.parse(res.body), { ok: true, sent: 3, accepted: true });
|
|
171
|
+
assert.equal(dispatches.length, 1);
|
|
172
|
+
// Malformed payloads are rejected by the wire codec, not dispatched.
|
|
172
173
|
const bad = await invokeProxy(proxy, makeProxyRequest({
|
|
173
174
|
path: '/_antseed/sweep',
|
|
174
175
|
body: { ...validSweep, sig3009: 'garbage' },
|
|
175
176
|
}));
|
|
176
177
|
assert.equal(bad.statusCode, 400);
|
|
177
|
-
assert.equal(
|
|
178
|
+
assert.equal(dispatches.length, 1);
|
|
178
179
|
// Receipts surfaced via node events are readable per-nonce.
|
|
179
180
|
const emit = listeners.get('sweep:receipt');
|
|
180
181
|
assert.ok(emit, 'proxy subscribes to sweep:receipt');
|
|
@@ -890,6 +891,113 @@ test('rewritePeerPinnedServiceInBody returns original when body is not JSON cont
|
|
|
890
891
|
assert.equal(result.headers, headers);
|
|
891
892
|
assert.equal(result.pinnedPeerId, null);
|
|
892
893
|
});
|
|
894
|
+
test('substituteRoutedModelAlias replaces the alias model with the default routed model', () => {
|
|
895
|
+
const body = makeJsonBody({ model: 'antseed', messages: [] });
|
|
896
|
+
const result = substituteRoutedModelAlias(body, jsonHeaders, `${validPeerId}@gpt-4o`);
|
|
897
|
+
assert.equal(result.aliasRequested, true);
|
|
898
|
+
assert.equal(result.substituted, true);
|
|
899
|
+
const parsed = parseJsonBody(result.body);
|
|
900
|
+
assert.equal(parsed['model'], `${validPeerId}@gpt-4o`);
|
|
901
|
+
});
|
|
902
|
+
test('substituteRoutedModelAlias handles the alias in the service field and is case-insensitive', () => {
|
|
903
|
+
const body = makeJsonBody({ service: 'AntSeed', messages: [] });
|
|
904
|
+
const result = substituteRoutedModelAlias(body, jsonHeaders, `${validPeerId}@gpt-4o`);
|
|
905
|
+
assert.equal(result.substituted, true);
|
|
906
|
+
const parsed = parseJsonBody(result.body);
|
|
907
|
+
assert.equal(parsed['service'], `${validPeerId}@gpt-4o`);
|
|
908
|
+
});
|
|
909
|
+
test('substituteRoutedModelAlias reports an unresolvable alias when no default route is set', () => {
|
|
910
|
+
const body = makeJsonBody({ model: 'antseed', messages: [] });
|
|
911
|
+
const result = substituteRoutedModelAlias(body, jsonHeaders, null);
|
|
912
|
+
assert.equal(result.aliasRequested, true);
|
|
913
|
+
assert.equal(result.substituted, false);
|
|
914
|
+
assert.equal(result.body, body);
|
|
915
|
+
});
|
|
916
|
+
test('substituteRoutedModelAlias leaves non-alias models untouched', () => {
|
|
917
|
+
const body = makeJsonBody({ model: 'gpt-4o', messages: [] });
|
|
918
|
+
const result = substituteRoutedModelAlias(body, jsonHeaders, `${validPeerId}@gpt-4o`);
|
|
919
|
+
assert.equal(result.aliasRequested, false);
|
|
920
|
+
assert.equal(result.substituted, false);
|
|
921
|
+
assert.equal(result.body, body);
|
|
922
|
+
});
|
|
923
|
+
test('overrideRoutedModelInBody swaps the model, mirrors an identical service field, and fixes content-length', () => {
|
|
924
|
+
const oldRoute = `${validPeerId}@gpt-4o`;
|
|
925
|
+
const newRoute = `${'bb'.repeat(20)}@glm-5`;
|
|
926
|
+
const body = makeJsonBody({ model: oldRoute, service: oldRoute, messages: [] });
|
|
927
|
+
const headers = { ...jsonHeaders, 'content-length': String(body.length) };
|
|
928
|
+
const result = overrideRoutedModelInBody(body, headers, newRoute);
|
|
929
|
+
assert.equal(result.overridden, true);
|
|
930
|
+
const parsed = parseJsonBody(result.body);
|
|
931
|
+
assert.equal(parsed['model'], newRoute);
|
|
932
|
+
assert.equal(parsed['service'], newRoute);
|
|
933
|
+
assert.equal(result.headers['content-length'], String(result.body.length));
|
|
934
|
+
});
|
|
935
|
+
test('overrideRoutedModelInBody leaves a differing service field alone', () => {
|
|
936
|
+
const body = makeJsonBody({ model: `${validPeerId}@gpt-4o`, service: 'something-else', messages: [] });
|
|
937
|
+
const result = overrideRoutedModelInBody(body, jsonHeaders, `${'bb'.repeat(20)}@glm-5`);
|
|
938
|
+
assert.equal(result.overridden, true);
|
|
939
|
+
const parsed = parseJsonBody(result.body);
|
|
940
|
+
assert.equal(parsed['model'], `${'bb'.repeat(20)}@glm-5`);
|
|
941
|
+
assert.equal(parsed['service'], 'something-else');
|
|
942
|
+
});
|
|
943
|
+
test('overrideRoutedModelInBody no-ops on a matching model, a missing model, or non-JSON bodies', () => {
|
|
944
|
+
const route = `${validPeerId}@gpt-4o`;
|
|
945
|
+
const matching = makeJsonBody({ model: route });
|
|
946
|
+
assert.equal(overrideRoutedModelInBody(matching, jsonHeaders, route).overridden, false);
|
|
947
|
+
const missing = makeJsonBody({ messages: [] });
|
|
948
|
+
assert.equal(overrideRoutedModelInBody(missing, jsonHeaders, route).overridden, false);
|
|
949
|
+
const nonJson = makeJsonBody({ model: 'other' });
|
|
950
|
+
assert.equal(overrideRoutedModelInBody(nonJson, { 'content-type': 'text/plain' }, route).overridden, false);
|
|
951
|
+
});
|
|
952
|
+
test('route control endpoint sets, persists, and returns the default routed model', async (t) => {
|
|
953
|
+
const dir = await mkdtemp(join(tmpdir(), 'antseed-buyer-route-'));
|
|
954
|
+
t.after(() => rm(dir, { recursive: true, force: true }));
|
|
955
|
+
const proxy = new BuyerProxy({
|
|
956
|
+
port: 0,
|
|
957
|
+
dataDir: dir,
|
|
958
|
+
node: { router: null },
|
|
959
|
+
});
|
|
960
|
+
const set = await invokeProxy(proxy, makeProxyRequest({ path: '/_antseed/route', body: { model: `${validPeerId}@gpt-4o` } }));
|
|
961
|
+
assert.equal(set.statusCode, 200);
|
|
962
|
+
assert.deepEqual(JSON.parse(set.body), { ok: true, model: `${validPeerId}@gpt-4o` });
|
|
963
|
+
const get = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: '/_antseed/route' }));
|
|
964
|
+
assert.deepEqual(JSON.parse(get.body), { ok: true, model: `${validPeerId}@gpt-4o` });
|
|
965
|
+
const persisted = JSON.parse(await readFile(join(dir, 'buyer.state.json'), 'utf-8'));
|
|
966
|
+
assert.equal(persisted['defaultRoutedModel'], `${validPeerId}@gpt-4o`);
|
|
967
|
+
const invalid = await invokeProxy(proxy, makeProxyRequest({ path: '/_antseed/route', body: { model: 'gpt-4o' } }));
|
|
968
|
+
assert.equal(invalid.statusCode, 400);
|
|
969
|
+
const cleared = await invokeProxy(proxy, makeProxyRequest({ path: '/_antseed/route', body: { model: '' } }));
|
|
970
|
+
assert.deepEqual(JSON.parse(cleared.body), { ok: true, model: null });
|
|
971
|
+
});
|
|
972
|
+
test('buyer-usage endpoint reports lastActivityAt, null until a request is dispatched', async () => {
|
|
973
|
+
const proxy = new BuyerProxy({
|
|
974
|
+
port: 0,
|
|
975
|
+
dataDir: '/tmp/antseed-test',
|
|
976
|
+
node: { router: null, getBuyerUsageTotals: () => ({ totalRequests: 0 }) },
|
|
977
|
+
});
|
|
978
|
+
const before = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: '/_antseed/buyer-usage' }));
|
|
979
|
+
assert.equal(before.statusCode, 200);
|
|
980
|
+
assert.equal(JSON.parse(before.body).lastActivityAt, null);
|
|
981
|
+
proxy._markModelActivity();
|
|
982
|
+
const after = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: '/_antseed/buyer-usage' }));
|
|
983
|
+
const parsed = JSON.parse(after.body);
|
|
984
|
+
assert.equal(parsed.ok, true);
|
|
985
|
+
assert.equal(typeof parsed.lastActivityAt, 'number');
|
|
986
|
+
assert.ok((parsed.lastActivityAt ?? 0) > 0);
|
|
987
|
+
});
|
|
988
|
+
test('requests with the routed-model alias fail clearly when no default route is set', async () => {
|
|
989
|
+
const proxy = makeBuyerProxyWithPeers([makePeer('a', ['openai'])]);
|
|
990
|
+
const res = await invokeProxy(proxy, makeProxyRequest({ body: { model: 'antseed', messages: [] } }));
|
|
991
|
+
assert.equal(res.statusCode, 400);
|
|
992
|
+
const parsed = JSON.parse(res.body);
|
|
993
|
+
assert.equal(parsed.error?.code, 'no_default_route');
|
|
994
|
+
});
|
|
995
|
+
test('substituteRoutedModelAlias updates content-length when substituting', () => {
|
|
996
|
+
const original = makeJsonBody({ model: 'antseed', messages: [] });
|
|
997
|
+
const headers = { 'content-type': 'application/json', 'content-length': String(original.length) };
|
|
998
|
+
const result = substituteRoutedModelAlias(original, headers, `${validPeerId}@gpt-4o`);
|
|
999
|
+
assert.equal(result.headers['content-length'], String(result.body.length));
|
|
1000
|
+
});
|
|
893
1001
|
test('rewritePeerPinnedServiceInBody returns original when body is empty', () => {
|
|
894
1002
|
const body = new Uint8Array(0);
|
|
895
1003
|
const result = rewritePeerPinnedServiceInBody(body, jsonHeaders);
|
|
@@ -902,6 +1010,269 @@ test('rewritePeerPinnedServiceInBody returns original when body is not a JSON ob
|
|
|
902
1010
|
assert.equal(result.body, body);
|
|
903
1011
|
assert.equal(result.pinnedPeerId, null);
|
|
904
1012
|
});
|
|
1013
|
+
// ---------- Per-chat conversations (tracking, pins, control endpoints) ----------
|
|
1014
|
+
async function makeConversationProxy() {
|
|
1015
|
+
const dir = await mkdtemp(join(tmpdir(), 'antseed-buyer-conv-'));
|
|
1016
|
+
const proxy = new BuyerProxy({
|
|
1017
|
+
port: 0,
|
|
1018
|
+
dataDir: dir,
|
|
1019
|
+
node: { router: null },
|
|
1020
|
+
});
|
|
1021
|
+
proxy._getPeers = async () => [];
|
|
1022
|
+
proxy._cacheLastUpdatedAtMs = Date.now();
|
|
1023
|
+
return { proxy, dir };
|
|
1024
|
+
}
|
|
1025
|
+
test('per-chat pin overrides the default routed model for the antseed alias', async () => {
|
|
1026
|
+
const { proxy, dir } = await makeConversationProxy();
|
|
1027
|
+
try {
|
|
1028
|
+
const defaultRoute = `${'aa'.repeat(20)}@default-model`;
|
|
1029
|
+
const pinnedRoute = `${'bb'.repeat(20)}@pinned-model`;
|
|
1030
|
+
proxy._defaultRoutedModel = defaultRoute;
|
|
1031
|
+
const store = proxy._conversations;
|
|
1032
|
+
store.touch({ tool: 'codex-exec', sessionKey: 'sess-1' });
|
|
1033
|
+
store.setPinnedModel('codex-exec:sess-1', pinnedRoute);
|
|
1034
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1035
|
+
path: '/v1/responses',
|
|
1036
|
+
headers: { originator: 'codex_exec', 'session-id': 'sess-1' },
|
|
1037
|
+
body: {
|
|
1038
|
+
model: 'antseed',
|
|
1039
|
+
input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: 'hello there' }] }],
|
|
1040
|
+
},
|
|
1041
|
+
}));
|
|
1042
|
+
// Routing itself fails (no peers), but the pin was applied during alias
|
|
1043
|
+
// substitution and recorded as the conversation's resolved model.
|
|
1044
|
+
const record = store.get('codex-exec:sess-1');
|
|
1045
|
+
assert.equal(record?.lastModel, pinnedRoute);
|
|
1046
|
+
// A different chat without a pin resolves to the default route.
|
|
1047
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1048
|
+
path: '/v1/responses',
|
|
1049
|
+
headers: { originator: 'codex_exec', 'session-id': 'sess-2' },
|
|
1050
|
+
body: {
|
|
1051
|
+
model: 'antseed',
|
|
1052
|
+
input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: 'second chat' }] }],
|
|
1053
|
+
},
|
|
1054
|
+
}));
|
|
1055
|
+
const second = store.get('codex-exec:sess-2');
|
|
1056
|
+
assert.equal(second?.lastModel, defaultRoute);
|
|
1057
|
+
assert.equal(second?.snippet, 'second chat');
|
|
1058
|
+
await store.flush();
|
|
1059
|
+
}
|
|
1060
|
+
finally {
|
|
1061
|
+
await rm(dir, { recursive: true, force: true });
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
test('chat pin overrides a system-proxy-routed model on intercepted requests', async () => {
|
|
1065
|
+
const { proxy, dir } = await makeConversationProxy();
|
|
1066
|
+
try {
|
|
1067
|
+
const proxyRoute = `${'aa'.repeat(20)}@default-model`;
|
|
1068
|
+
const pinnedRoute = `${'bb'.repeat(20)}@pinned-model`;
|
|
1069
|
+
const store = proxy._conversations;
|
|
1070
|
+
// First intercepted request: the system proxy already rewrote the tool's
|
|
1071
|
+
// upstream model to its connect-time route and marked the request. The
|
|
1072
|
+
// chat auto-pins to the model that served it.
|
|
1073
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1074
|
+
path: '/v1/messages',
|
|
1075
|
+
headers: { 'x-claude-code-session-id': 'cc-1', [SYSTEM_ROUTED_MODEL_HEADER]: '1' },
|
|
1076
|
+
body: { model: proxyRoute, messages: [{ role: 'user', content: 'hello there' }] },
|
|
1077
|
+
}));
|
|
1078
|
+
assert.equal(store.get('claude-code:cc-1')?.pinnedModel, proxyRoute);
|
|
1079
|
+
// The user re-pins the chat from the desktop (float / chats view).
|
|
1080
|
+
store.setPinnedModel('claude-code:cc-1', pinnedRoute);
|
|
1081
|
+
// Later requests still arrive with the proxy-assigned model; the pin wins.
|
|
1082
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1083
|
+
path: '/v1/messages',
|
|
1084
|
+
headers: { 'x-claude-code-session-id': 'cc-1', [SYSTEM_ROUTED_MODEL_HEADER]: '1' },
|
|
1085
|
+
body: { model: proxyRoute, messages: [{ role: 'user', content: 'again' }] },
|
|
1086
|
+
}));
|
|
1087
|
+
assert.equal(store.get('claude-code:cc-1')?.lastModel, pinnedRoute);
|
|
1088
|
+
// Without the marker the model is a client choice and is respected.
|
|
1089
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1090
|
+
path: '/v1/messages',
|
|
1091
|
+
headers: { 'x-claude-code-session-id': 'cc-1' },
|
|
1092
|
+
body: { model: proxyRoute, messages: [{ role: 'user', content: 'explicit' }] },
|
|
1093
|
+
}));
|
|
1094
|
+
assert.equal(store.get('claude-code:cc-1')?.lastModel, proxyRoute);
|
|
1095
|
+
await store.flush();
|
|
1096
|
+
}
|
|
1097
|
+
finally {
|
|
1098
|
+
await rm(dir, { recursive: true, force: true });
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
test('count_tokens is answered locally and never reaches a seller', async () => {
|
|
1102
|
+
const { proxy, dir } = await makeConversationProxy();
|
|
1103
|
+
try {
|
|
1104
|
+
let routed = 0;
|
|
1105
|
+
proxy._getPeers = async () => { routed += 1; return []; };
|
|
1106
|
+
const res = await invokeProxy(proxy, makeProxyRequest({
|
|
1107
|
+
path: '/v1/messages/count_tokens',
|
|
1108
|
+
headers: { 'x-claude-code-session-id': 'cc-count' },
|
|
1109
|
+
body: {
|
|
1110
|
+
model: 'claude-sonnet-4-5',
|
|
1111
|
+
system: 'You are a CLI assistant. '.repeat(40),
|
|
1112
|
+
messages: [{ role: 'user', content: 'how big is this conversation?' }],
|
|
1113
|
+
},
|
|
1114
|
+
}));
|
|
1115
|
+
assert.equal(res.statusCode, 200);
|
|
1116
|
+
const body = JSON.parse(res.body);
|
|
1117
|
+
assert.ok(body.input_tokens > 50, `expected a token count, got ${JSON.stringify(body)}`);
|
|
1118
|
+
assert.equal(routed, 0, 'count_tokens must not route to a peer');
|
|
1119
|
+
// It is a probe about a chat, not a turn in one.
|
|
1120
|
+
assert.deepEqual(proxy._conversations.list(), []);
|
|
1121
|
+
await proxy._conversations.flush();
|
|
1122
|
+
}
|
|
1123
|
+
finally {
|
|
1124
|
+
await rm(dir, { recursive: true, force: true });
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
test('a thread the tool opened for itself never becomes a chat', async () => {
|
|
1128
|
+
const { proxy, dir } = await makeConversationProxy();
|
|
1129
|
+
try {
|
|
1130
|
+
const store = proxy._conversations;
|
|
1131
|
+
const turnMetadata = (threadId, threadSource) => JSON.stringify({ thread_id: threadId, request_kind: 'turn', thread_source: threadSource });
|
|
1132
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1133
|
+
path: '/v1/responses',
|
|
1134
|
+
headers: {
|
|
1135
|
+
originator: 'Codex Desktop',
|
|
1136
|
+
'thread-id': 'thread-real',
|
|
1137
|
+
'x-codex-turn-metadata': turnMetadata('thread-real', 'user'),
|
|
1138
|
+
},
|
|
1139
|
+
body: { input: [{ type: 'message', role: 'user', content: [{ type: 'input_text', text: 'yo' }] }] },
|
|
1140
|
+
}));
|
|
1141
|
+
// Codex titles the chat from a system thread of its own, milliseconds later.
|
|
1142
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1143
|
+
path: '/v1/responses',
|
|
1144
|
+
headers: {
|
|
1145
|
+
originator: 'Codex Desktop',
|
|
1146
|
+
'thread-id': 'thread-title',
|
|
1147
|
+
'x-codex-turn-metadata': turnMetadata('thread-title', 'system'),
|
|
1148
|
+
},
|
|
1149
|
+
body: { input: 'You are a helpful assistant... provide a short title...\n\nUser prompt:\nyo' },
|
|
1150
|
+
}));
|
|
1151
|
+
assert.deepEqual(store.list().map((c) => c.id), ['codex-desktop:thread-real']);
|
|
1152
|
+
assert.equal(store.get('codex-desktop:thread-real')?.snippet, 'yo');
|
|
1153
|
+
await store.flush();
|
|
1154
|
+
}
|
|
1155
|
+
finally {
|
|
1156
|
+
await rm(dir, { recursive: true, force: true });
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
test('conversation control endpoints list, rename, pin, reject bad pins, delete', async () => {
|
|
1160
|
+
const { proxy, dir } = await makeConversationProxy();
|
|
1161
|
+
try {
|
|
1162
|
+
const store = proxy._conversations;
|
|
1163
|
+
store.touch({ tool: 'opencode', sessionKey: 'ses_x', snippet: 'refactor the login flow' });
|
|
1164
|
+
const list = await invokeProxy(proxy, makeProxyRequest({ method: 'GET', path: '/_antseed/conversations' }));
|
|
1165
|
+
assert.equal(list.statusCode, 200);
|
|
1166
|
+
const listed = JSON.parse(list.body);
|
|
1167
|
+
assert.equal(listed.ok, true);
|
|
1168
|
+
assert.equal(listed.conversations.length, 1);
|
|
1169
|
+
assert.equal(listed.conversations[0]?.id, 'opencode:ses_x');
|
|
1170
|
+
const rename = await invokeProxy(proxy, makeProxyRequest({
|
|
1171
|
+
path: '/_antseed/conversations/update',
|
|
1172
|
+
body: { id: 'opencode:ses_x', label: 'Login refactor' },
|
|
1173
|
+
}));
|
|
1174
|
+
assert.equal(rename.statusCode, 200);
|
|
1175
|
+
assert.equal(store.get('opencode:ses_x')?.label, 'Login refactor');
|
|
1176
|
+
const badPin = await invokeProxy(proxy, makeProxyRequest({
|
|
1177
|
+
path: '/_antseed/conversations/update',
|
|
1178
|
+
body: { id: 'opencode:ses_x', pinnedModel: 'not-a-route' },
|
|
1179
|
+
}));
|
|
1180
|
+
assert.equal(badPin.statusCode, 400);
|
|
1181
|
+
const goodPin = await invokeProxy(proxy, makeProxyRequest({
|
|
1182
|
+
path: '/_antseed/conversations/update',
|
|
1183
|
+
body: { id: 'opencode:ses_x', pinnedModel: `${'cc'.repeat(20)}@glm-5` },
|
|
1184
|
+
}));
|
|
1185
|
+
assert.equal(goodPin.statusCode, 200);
|
|
1186
|
+
assert.equal(store.getPinnedModel('opencode', 'ses_x'), `${'cc'.repeat(20)}@glm-5`);
|
|
1187
|
+
const clearPin = await invokeProxy(proxy, makeProxyRequest({
|
|
1188
|
+
path: '/_antseed/conversations/update',
|
|
1189
|
+
body: { id: 'opencode:ses_x', pinnedModel: '' },
|
|
1190
|
+
}));
|
|
1191
|
+
assert.equal(clearPin.statusCode, 200);
|
|
1192
|
+
assert.equal(store.getPinnedModel('opencode', 'ses_x'), null);
|
|
1193
|
+
const missing = await invokeProxy(proxy, makeProxyRequest({
|
|
1194
|
+
path: '/_antseed/conversations/update',
|
|
1195
|
+
body: { id: 'nope:missing', label: 'x' },
|
|
1196
|
+
}));
|
|
1197
|
+
assert.equal(missing.statusCode, 404);
|
|
1198
|
+
const removed = await invokeProxy(proxy, makeProxyRequest({
|
|
1199
|
+
path: '/_antseed/conversations/update',
|
|
1200
|
+
body: { id: 'opencode:ses_x', delete: true },
|
|
1201
|
+
}));
|
|
1202
|
+
assert.equal(removed.statusCode, 200);
|
|
1203
|
+
assert.equal(store.get('opencode:ses_x'), null);
|
|
1204
|
+
await store.flush();
|
|
1205
|
+
}
|
|
1206
|
+
finally {
|
|
1207
|
+
await rm(dir, { recursive: true, force: true });
|
|
1208
|
+
}
|
|
1209
|
+
});
|
|
1210
|
+
test('subagent requests roll up into the parent conversation', async () => {
|
|
1211
|
+
const { proxy, dir } = await makeConversationProxy();
|
|
1212
|
+
try {
|
|
1213
|
+
;
|
|
1214
|
+
proxy._defaultRoutedModel = `${'aa'.repeat(20)}@default-model`;
|
|
1215
|
+
const store = proxy._conversations;
|
|
1216
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1217
|
+
path: '/v1/chat/completions',
|
|
1218
|
+
headers: { 'user-agent': 'opencode/1.0', 'x-session-id': 'ses_child', 'x-parent-session-id': 'ses_parent' },
|
|
1219
|
+
body: { model: 'antseed', messages: [{ role: 'user', content: 'subtask prompt' }] },
|
|
1220
|
+
}));
|
|
1221
|
+
assert.equal(store.get('opencode:ses_child'), null);
|
|
1222
|
+
assert.notEqual(store.get('opencode:ses_parent'), null);
|
|
1223
|
+
await store.flush();
|
|
1224
|
+
}
|
|
1225
|
+
finally {
|
|
1226
|
+
await rm(dir, { recursive: true, force: true });
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1229
|
+
test('title request racing ahead of the first turn does not name the chat', async () => {
|
|
1230
|
+
const { proxy, dir } = await makeConversationProxy();
|
|
1231
|
+
try {
|
|
1232
|
+
;
|
|
1233
|
+
proxy._defaultRoutedModel = `${'aa'.repeat(20)}@default-model`;
|
|
1234
|
+
const store = proxy._conversations;
|
|
1235
|
+
// OpenCode's ensureTitle request lands first, on the same session.
|
|
1236
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1237
|
+
path: '/v1/chat/completions',
|
|
1238
|
+
headers: { 'user-agent': 'opencode/1.0', 'x-session-id': 'ses_race' },
|
|
1239
|
+
body: {
|
|
1240
|
+
model: 'antseed',
|
|
1241
|
+
messages: [
|
|
1242
|
+
{ role: 'user', content: 'Generate a title for this conversation:\n' },
|
|
1243
|
+
{ role: 'user', content: 'hi' },
|
|
1244
|
+
],
|
|
1245
|
+
},
|
|
1246
|
+
}));
|
|
1247
|
+
// Title-only housekeeping routes normally but no longer creates the row.
|
|
1248
|
+
assert.equal(store.get('opencode:ses_race'), null);
|
|
1249
|
+
// A Claude/T3 Code-style pure title request routes normally but does not
|
|
1250
|
+
// create a blank conversation row.
|
|
1251
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1252
|
+
path: '/v1/messages',
|
|
1253
|
+
headers: { 'user-agent': 'claude-cli/2.0 (external)', 'x-claude-code-session-id': 'cc_race' },
|
|
1254
|
+
body: {
|
|
1255
|
+
model: 'antseed',
|
|
1256
|
+
messages: [{ role: 'user', content: 'You write concise thread titles for a coding chat.' }],
|
|
1257
|
+
},
|
|
1258
|
+
}));
|
|
1259
|
+
assert.equal(store.get('claude-code:cc_race'), null);
|
|
1260
|
+
// The real first turn creates the conversation afterwards.
|
|
1261
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
1262
|
+
path: '/v1/messages',
|
|
1263
|
+
headers: { 'user-agent': 'claude-cli/2.0 (external)', 'x-claude-code-session-id': 'cc_race' },
|
|
1264
|
+
body: {
|
|
1265
|
+
model: 'antseed',
|
|
1266
|
+
messages: [{ role: 'user', content: 'fix the login bug' }],
|
|
1267
|
+
},
|
|
1268
|
+
}));
|
|
1269
|
+
assert.equal(store.get('claude-code:cc_race')?.snippet, 'fix the login bug');
|
|
1270
|
+
await store.flush();
|
|
1271
|
+
}
|
|
1272
|
+
finally {
|
|
1273
|
+
await rm(dir, { recursive: true, force: true });
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
905
1276
|
test('makeVerifierReach: rejects non-attest paths, sends the attest route as a payment-free control-plane request', async () => {
|
|
906
1277
|
const peer = makePeer('a', ['openai']);
|
|
907
1278
|
const signal = new AbortController().signal;
|