@antseed/cli 0.1.154 → 0.1.156
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/tunnel/index.d.ts +3 -0
- package/dist/cli/commands/tunnel/index.d.ts.map +1 -0
- package/dist/cli/commands/tunnel/index.js +10 -0
- package/dist/cli/commands/tunnel/index.js.map +1 -0
- package/dist/cli/commands/tunnel/paths.d.ts +4 -0
- package/dist/cli/commands/tunnel/paths.d.ts.map +1 -0
- package/dist/cli/commands/tunnel/paths.js +5 -0
- package/dist/cli/commands/tunnel/paths.js.map +1 -0
- package/dist/cli/commands/tunnel/start.d.ts +3 -0
- package/dist/cli/commands/tunnel/start.d.ts.map +1 -0
- package/dist/cli/commands/tunnel/start.js +235 -0
- package/dist/cli/commands/tunnel/start.js.map +1 -0
- package/dist/cli/commands/tunnel/status.d.ts +3 -0
- package/dist/cli/commands/tunnel/status.d.ts.map +1 -0
- package/dist/cli/commands/tunnel/status.js +39 -0
- package/dist/cli/commands/tunnel/status.js.map +1 -0
- package/dist/cli/commands/tunnel/stop.d.ts +3 -0
- package/dist/cli/commands/tunnel/stop.d.ts.map +1 -0
- package/dist/cli/commands/tunnel/stop.js +28 -0
- package/dist/cli/commands/tunnel/stop.js.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/proxy/buyer-proxy.d.ts.map +1 -1
- package/dist/proxy/buyer-proxy.js +7 -1
- package/dist/proxy/buyer-proxy.js.map +1 -1
- package/dist/proxy/buyer-proxy.test.js +112 -1
- package/dist/proxy/buyer-proxy.test.js.map +1 -1
- package/dist/proxy/conversation-identity.d.ts +3 -1
- package/dist/proxy/conversation-identity.d.ts.map +1 -1
- package/dist/proxy/conversation-identity.js +80 -7
- package/dist/proxy/conversation-identity.js.map +1 -1
- package/dist/proxy/conversation-identity.test.js +146 -0
- package/dist/proxy/conversation-identity.test.js.map +1 -1
- package/dist/proxy/conversation-store.d.ts.map +1 -1
- package/dist/proxy/conversation-store.js +34 -6
- package/dist/proxy/conversation-store.js.map +1 -1
- package/dist/proxy/conversation-store.test.js +58 -0
- package/dist/proxy/conversation-store.test.js.map +1 -1
- package/dist/system-proxy/profiles.d.ts.map +1 -1
- package/dist/system-proxy/profiles.js +4 -1
- package/dist/system-proxy/profiles.js.map +1 -1
- package/dist/system-proxy/types.d.ts +2 -1
- package/dist/system-proxy/types.d.ts.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +2 -0
- package/dist/tunnel/cloudflared.d.ts.map +1 -0
- package/dist/tunnel/cloudflared.js +57 -0
- package/dist/tunnel/cloudflared.js.map +1 -0
- package/dist/tunnel/gateway.d.ts +16 -0
- package/dist/tunnel/gateway.d.ts.map +1 -0
- package/dist/tunnel/gateway.js +146 -0
- package/dist/tunnel/gateway.js.map +1 -0
- package/dist/tunnel/gateway.test.d.ts +2 -0
- package/dist/tunnel/gateway.test.d.ts.map +1 -0
- package/dist/tunnel/gateway.test.js +89 -0
- package/dist/tunnel/gateway.test.js.map +1 -0
- package/package.json +4 -4
|
@@ -8,7 +8,7 @@ import test from 'node:test';
|
|
|
8
8
|
import { ANTSEED_BUYER_FAULT_ERROR_CODE, ANTSEED_FAULT_ATTRIBUTION_HEADER, CONNECTION_CAPABILITY_COOPERATIVE_CLOSE_V1, CONNECTION_CAPABILITY_RELAYS_SWEEPS_V1, buyerFault, computeOnChainReputationScore, } from '@antseed/node';
|
|
9
9
|
import { DEFAULT_BUYER_PEER_REFRESH_INTERVAL_MS } from '../config/defaults.js';
|
|
10
10
|
import { BuyerProxy, isModelNotFoundResponse, makeVerifierReach, mergeJsonStateFile, parsePeerPinnedService, parsePersistedPeers, rewritePeerPinnedServiceInBody, sanitizePeerBuyerFaultMarker, selectCandidatePeersForRouting, substituteRoutedModelAlias, sweepStaleStateTmpFiles, } from './buyer-proxy.js';
|
|
11
|
-
import { extractRequestedService, overrideRoutedModelInBody, SYSTEM_ROUTED_MODEL_HEADER } from './request-utils.js';
|
|
11
|
+
import { extractRequestedService, overrideRoutedModelInBody, SYSTEM_ROUTED_MODEL_HEADER, } from './request-utils.js';
|
|
12
12
|
function makePeer(seed, providers) {
|
|
13
13
|
const repeated = (seed.repeat(40) + 'a'.repeat(40)).slice(0, 40);
|
|
14
14
|
return {
|
|
@@ -1069,6 +1069,64 @@ test('pinned proxy request reports when the pinned peer is not discoverable', as
|
|
|
1069
1069
|
assert.match(res.body, /is not reachable right now/);
|
|
1070
1070
|
assert.match(res.body, /It may be offline, not announcing, or temporarily unreachable/);
|
|
1071
1071
|
});
|
|
1072
|
+
test('pinned proxy request surfaces a 403 without failing over to another peer', async () => {
|
|
1073
|
+
// An explicit pin is a hard constraint: even with another peer serving the
|
|
1074
|
+
// same model, the pinned peer's error is surfaced rather than re-routed.
|
|
1075
|
+
const pinnedPeer = makePeer('a', ['openai']);
|
|
1076
|
+
pinnedPeer.providerServiceApiProtocols = {
|
|
1077
|
+
openai: { services: { 'gpt-5': ['openai-chat-completions'] } },
|
|
1078
|
+
};
|
|
1079
|
+
const otherPeer = makePeer('b', ['openai']);
|
|
1080
|
+
otherPeer.providerServiceApiProtocols = {
|
|
1081
|
+
openai: { services: { 'GPT 5': ['openai-chat-completions'] } },
|
|
1082
|
+
};
|
|
1083
|
+
const proxy = makeBuyerProxyWithPeers([pinnedPeer, otherPeer], [pinnedPeer, otherPeer], permissiveRouter());
|
|
1084
|
+
const attempts = [];
|
|
1085
|
+
proxy._node.sendRequest = async (peer, request) => {
|
|
1086
|
+
attempts.push(peer.peerId);
|
|
1087
|
+
return {
|
|
1088
|
+
requestId: request.requestId,
|
|
1089
|
+
statusCode: 403,
|
|
1090
|
+
headers: { 'content-type': 'text/html' },
|
|
1091
|
+
body: Buffer.from('<html><body><h1>403 Forbidden</h1></body></html>'),
|
|
1092
|
+
};
|
|
1093
|
+
};
|
|
1094
|
+
const res = await invokeProxy(proxy, makeProxyRequest({
|
|
1095
|
+
headers: { 'x-antseed-pin-peer': pinnedPeer.peerId },
|
|
1096
|
+
body: { model: 'gpt-5', messages: [] },
|
|
1097
|
+
}));
|
|
1098
|
+
assert.equal(res.statusCode, 403);
|
|
1099
|
+
assert.deepEqual(attempts, [pinnedPeer.peerId]);
|
|
1100
|
+
});
|
|
1101
|
+
test('model-only routing fails over to the next peer after a 401 or 403', async () => {
|
|
1102
|
+
for (const statusCode of [401, 403]) {
|
|
1103
|
+
const first = makePeer('a', ['openai']);
|
|
1104
|
+
first.reputationScore = 95;
|
|
1105
|
+
first.providerServiceApiProtocols = {
|
|
1106
|
+
openai: { services: { 'gpt-5': ['openai-chat-completions'] } },
|
|
1107
|
+
};
|
|
1108
|
+
const second = makePeer('b', ['openai']);
|
|
1109
|
+
second.reputationScore = 80;
|
|
1110
|
+
second.providerServiceApiProtocols = {
|
|
1111
|
+
openai: { services: { 'GPT 5': ['openai-chat-completions'] } },
|
|
1112
|
+
};
|
|
1113
|
+
const proxy = makeBuyerProxyWithPeers([first, second], [first, second], permissiveRouter());
|
|
1114
|
+
const attempts = [];
|
|
1115
|
+
proxy._node.sendRequest = async (peer, request) => {
|
|
1116
|
+
attempts.push(peer.peerId);
|
|
1117
|
+
return {
|
|
1118
|
+
requestId: request.requestId,
|
|
1119
|
+
statusCode: peer.peerId === first.peerId ? statusCode : 200,
|
|
1120
|
+
headers: { 'content-type': 'application/json' },
|
|
1121
|
+
body: Buffer.from(JSON.stringify({ peerId: peer.peerId })),
|
|
1122
|
+
};
|
|
1123
|
+
};
|
|
1124
|
+
const res = await invokeProxy(proxy, makeProxyRequest({ body: { model: 'gpt-5', messages: [] } }));
|
|
1125
|
+
assert.equal(res.statusCode, 200, `expected failover to succeed for upstream ${statusCode}`);
|
|
1126
|
+
assert.deepEqual(attempts, [first.peerId, second.peerId]);
|
|
1127
|
+
assert.equal(JSON.parse(res.body).peerId, second.peerId);
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1072
1130
|
test('pinned proxy request rewrites a canonical alias to the advertised service id', async () => {
|
|
1073
1131
|
const pinnedPeer = makePeer('a', ['openai']);
|
|
1074
1132
|
pinnedPeer.providerServiceApiProtocols = {
|
|
@@ -2514,6 +2572,59 @@ test('title request racing ahead of the first turn does not name the chat', asyn
|
|
|
2514
2572
|
},
|
|
2515
2573
|
}));
|
|
2516
2574
|
assert.equal(store.get('claude-code:cc_race'), null);
|
|
2575
|
+
// Factory/Droid has no session header. Its main turn and concurrent
|
|
2576
|
+
// one-shot title request therefore hash to different synthetic keys. The
|
|
2577
|
+
// system-role title instruction must keep the second key out of the list.
|
|
2578
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
2579
|
+
path: '/v1/chat/completions',
|
|
2580
|
+
headers: { originator: 'droid', 'user-agent': 'factory-cli/0.202.0' },
|
|
2581
|
+
body: {
|
|
2582
|
+
model: 'antseed',
|
|
2583
|
+
messages: [
|
|
2584
|
+
{ role: 'system', content: 'You are Droid, an AI software engineering agent.' },
|
|
2585
|
+
{ role: 'user', content: 'wowow' },
|
|
2586
|
+
],
|
|
2587
|
+
},
|
|
2588
|
+
}));
|
|
2589
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
2590
|
+
path: '/v1/chat/completions',
|
|
2591
|
+
headers: {
|
|
2592
|
+
originator: 'droid',
|
|
2593
|
+
'user-agent': 'factory-cli/0.202.0',
|
|
2594
|
+
},
|
|
2595
|
+
body: {
|
|
2596
|
+
model: 'antseed',
|
|
2597
|
+
messages: [
|
|
2598
|
+
{
|
|
2599
|
+
role: 'system',
|
|
2600
|
+
content: `Shared provider compatibility preamble.
|
|
2601
|
+
You are a helper that generates concise session titles for a session picker.
|
|
2602
|
+
Input: one user message from the start of a session.`,
|
|
2603
|
+
},
|
|
2604
|
+
{ role: 'user', content: 'wowow' },
|
|
2605
|
+
],
|
|
2606
|
+
},
|
|
2607
|
+
}));
|
|
2608
|
+
assert.equal(store.list().filter((conversation) => conversation.tool === 'droid').length, 1);
|
|
2609
|
+
// The exact helper marker describes housekeeping regardless of which
|
|
2610
|
+
// integration sends it, so it does not create another conversation row.
|
|
2611
|
+
await invokeProxy(proxy, makeProxyRequest({
|
|
2612
|
+
path: '/v1/chat/completions',
|
|
2613
|
+
headers: { originator: 'other-agent', 'user-agent': 'other-agent/1.0' },
|
|
2614
|
+
body: {
|
|
2615
|
+
model: 'antseed',
|
|
2616
|
+
messages: [
|
|
2617
|
+
{
|
|
2618
|
+
role: 'system',
|
|
2619
|
+
content: `Shared provider compatibility preamble.
|
|
2620
|
+
You are a helper that generates concise session titles for a session picker.
|
|
2621
|
+
Input: one user message from the start of a session.`,
|
|
2622
|
+
},
|
|
2623
|
+
{ role: 'user', content: 'wowow' },
|
|
2624
|
+
],
|
|
2625
|
+
},
|
|
2626
|
+
}));
|
|
2627
|
+
assert.equal(store.list().filter((conversation) => conversation.tool === 'other-agent').length, 0);
|
|
2517
2628
|
// The real first turn creates the conversation afterwards.
|
|
2518
2629
|
await invokeProxy(proxy, makeProxyRequest({
|
|
2519
2630
|
path: '/v1/messages',
|