@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.
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 +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 broadcasts via the running node', async () => {
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 broadcasts = [];
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
- broadcastSweepRequest: (payload) => {
162
- broadcasts.push(payload);
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(broadcasts.length, 1);
171
- // Malformed payloads are rejected by the wire codec, not broadcast.
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(broadcasts.length, 1);
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;