@nyte-ai/plugin 0.0.0-stage → 0.0.19
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/LICENSE +21 -0
- package/THIRD-PARTY-NOTICES.md +866 -0
- package/dist/chunks/billing-0y7dfkp2.js +146 -0
- package/dist/chunks/billing-2v3g7rd9.js +190 -0
- package/dist/chunks/billing-3j77ss7y.js +86 -0
- package/dist/chunks/billing-3scqmkzb.js +559 -0
- package/dist/chunks/billing-4vz9pxyx.js +22 -0
- package/dist/chunks/billing-6z2kgtyt.js +51 -0
- package/dist/chunks/billing-8rjk35gr.js +444 -0
- package/dist/chunks/billing-9nn3yp18.js +176 -0
- package/dist/chunks/billing-9qc0eqh4.js +174 -0
- package/dist/chunks/billing-a9vqn66p.js +351 -0
- package/dist/chunks/billing-bv5zr32b.js +369 -0
- package/dist/chunks/billing-cy1rg619.js +302 -0
- package/dist/chunks/billing-ejx2hv52.js +323 -0
- package/dist/chunks/billing-h13r0byk.js +80 -0
- package/dist/chunks/billing-hg64tqf1.js +97 -0
- package/dist/chunks/billing-j643d12p.js +197 -0
- package/dist/chunks/billing-kn3d30b4.js +276 -0
- package/dist/chunks/billing-mvc33pv6.js +36 -0
- package/dist/chunks/billing-s4rrspsk.js +7 -0
- package/dist/chunks/billing-svrhh5a5.js +307 -0
- package/dist/chunks/billing-sxc70w72.js +161 -0
- package/dist/chunks/billing-t1qrxe16.js +41 -0
- package/dist/chunks/billing-tm34gm2p.js +528 -0
- package/dist/chunks/billing-tzr3cebf.js +20 -0
- package/dist/chunks/billing-v3hg5rc7.js +373 -0
- package/dist/chunks/billing-wmcbg6fw.js +52 -0
- package/dist/chunks/billing-ye3wk5r3.js +205 -0
- package/dist/chunks/billing-zfpm2rt0.js +29 -0
- package/dist/examples/claude-code/LICENSE +21 -0
- package/dist/examples/claude-code/billing.d.ts +7 -0
- package/dist/examples/claude-code/billing.js +11 -0
- package/dist/examples/claude-code/claude-cli.d.ts +35 -0
- package/dist/examples/claude-code/claude-cli.js +15 -0
- package/dist/examples/claude-code/env.d.ts +1 -0
- package/dist/examples/claude-code/env.js +6 -0
- package/dist/examples/claude-code/index.d.ts +24 -0
- package/dist/examples/claude-code/index.js +149 -0
- package/dist/examples/claude-code/inert-mcp.d.ts +1 -0
- package/dist/examples/claude-code/inert-mcp.js +6 -0
- package/dist/examples/claude-code/model-catalog.d.ts +27 -0
- package/dist/examples/claude-code/model-catalog.js +12 -0
- package/dist/examples/claude-code/process.d.ts +22 -0
- package/dist/examples/claude-code/process.js +25 -0
- package/dist/examples/claude-code/replay.d.ts +4 -0
- package/dist/examples/claude-code/replay.js +11 -0
- package/dist/examples/claude-code/stream.d.ts +3 -0
- package/dist/examples/claude-code/stream.js +16 -0
- package/dist/examples/claude-code/transcript.d.ts +83 -0
- package/dist/examples/claude-code/transcript.js +23 -0
- package/dist/examples/claude-code/turn-relay.d.ts +48 -0
- package/dist/examples/claude-code/turn-relay.js +9 -0
- package/dist/examples/claude-code/wire.d.ts +372 -0
- package/dist/examples/claude-code/wire.js +18 -0
- package/dist/src/acp/connection.d.ts +24 -0
- package/dist/src/acp/connection.js +8 -0
- package/dist/src/acp/index.d.ts +54 -0
- package/dist/src/acp/index.js +1005 -0
- package/dist/src/acp/registry.d.ts +18 -0
- package/dist/src/acp/registry.js +9 -0
- package/dist/src/acp/thread.d.ts +46 -0
- package/dist/src/acp/thread.js +7 -0
- package/dist/src/acp/transcript.d.ts +8 -0
- package/dist/src/acp/transcript.js +11 -0
- package/dist/src/acp/translate.d.ts +33 -0
- package/dist/src/acp/translate.js +20 -0
- package/dist/src/approvals/approvals.d.ts +189 -0
- package/dist/src/approvals/approvals.js +143 -0
- package/dist/src/approvals/schema.d.ts +169 -0
- package/dist/src/approvals/schema.js +18 -0
- package/dist/src/bash-description.d.ts +30 -0
- package/dist/src/bash-description.js +18 -0
- package/dist/src/browser-tools.d.ts +25 -0
- package/dist/src/browser-tools.js +246 -0
- package/dist/src/canvas/canvas-compile.d.ts +48 -0
- package/dist/src/canvas/canvas-compile.js +8 -0
- package/dist/src/canvas/canvas-libraries.d.ts +32 -0
- package/dist/src/canvas/canvas-libraries.js +6 -0
- package/dist/src/canvas/canvas-preview.d.ts +107 -0
- package/dist/src/canvas/canvas-preview.js +12 -0
- package/dist/src/canvas/canvas-protocol.d.ts +184 -0
- package/dist/src/canvas/canvas-protocol.js +75 -0
- package/dist/src/canvas/canvas.d.ts +50 -0
- package/dist/src/canvas/canvas.js +221 -0
- package/dist/src/canvas/schema.d.ts +47 -0
- package/dist/src/canvas/schema.js +8 -0
- package/dist/src/claude-code-sdk/index.d.ts +35 -0
- package/dist/src/claude-code-sdk/index.js +379 -0
- package/dist/src/claude-code-sdk/session.d.ts +50 -0
- package/dist/src/claude-code-sdk/session.js +7 -0
- package/dist/src/claude-code-sdk/transcript.d.ts +19 -0
- package/dist/src/claude-code-sdk/transcript.js +15 -0
- package/dist/src/claude-code-sdk/translate.d.ts +18 -0
- package/dist/src/claude-code-sdk/translate.js +20 -0
- package/dist/src/client/mount.d.ts +44 -0
- package/dist/src/client/mount.js +252 -0
- package/dist/src/client/notifications.d.ts +36 -0
- package/dist/src/client/notifications.js +24 -0
- package/dist/src/client/presentation.d.ts +362 -0
- package/dist/src/client/presentation.js +391 -0
- package/dist/src/client/transcript-tui.d.ts +23 -0
- package/dist/src/client/transcript-tui.js +20 -0
- package/dist/src/client/transcript.d.ts +23 -0
- package/dist/src/client/transcript.js +51 -0
- package/dist/src/client.d.ts +53 -0
- package/dist/src/client.js +22 -0
- package/dist/src/codemode-runtime.d.ts +11 -0
- package/dist/src/codemode-runtime.js +10 -0
- package/dist/src/codemode-worker.d.ts +1 -0
- package/dist/src/codemode-worker.js +2 -0
- package/dist/src/codemode.d.ts +21 -0
- package/dist/src/codemode.js +170 -0
- package/dist/src/core.d.ts +43 -0
- package/dist/src/core.js +11 -0
- package/dist/src/find-on-path.d.ts +4 -0
- package/dist/src/find-on-path.js +8 -0
- package/dist/src/openai-astra-context.d.ts +23 -0
- package/dist/src/openai-astra-context.js +14 -0
- package/dist/src/openai-compaction.d.ts +15 -0
- package/dist/src/openai-compaction.js +55 -0
- package/dist/src/question/client/questions-tui.d.ts +9 -0
- package/dist/src/question/client/questions-tui.js +19 -0
- package/dist/src/question/client/questions.d.ts +9 -0
- package/dist/src/question/client/questions.js +160 -0
- package/dist/src/question/question.d.ts +63 -0
- package/dist/src/question/question.js +9 -0
- package/dist/src/question/schema.d.ts +101 -0
- package/dist/src/question/schema.js +14 -0
- package/dist/src/rename.d.ts +79 -0
- package/dist/src/rename.js +91 -0
- package/dist/src/telemetry.d.ts +36 -0
- package/dist/src/telemetry.js +84 -0
- package/dist/src/web-search.d.ts +78 -0
- package/dist/src/web-search.js +219 -0
- package/package.json +190 -3
- package/README.md +0 -3
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// examples/claude-code/model-catalog.ts
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
var PROVIDER_ID2 = "anthropic";
|
|
4
|
+
var API_ID2 = "claude-code-stream-json";
|
|
5
|
+
var discoveredModelSchema2 = Type.Object({
|
|
6
|
+
value: Type.String(),
|
|
7
|
+
resolvedModel: Type.Optional(Type.String()),
|
|
8
|
+
displayName: Type.String(),
|
|
9
|
+
description: Type.String(),
|
|
10
|
+
supportsEffort: Type.Optional(Type.Boolean()),
|
|
11
|
+
supportedEffortLevels: Type.Optional(Type.Array(Type.Union([
|
|
12
|
+
Type.Literal("low"),
|
|
13
|
+
Type.Literal("medium"),
|
|
14
|
+
Type.Literal("high"),
|
|
15
|
+
Type.Literal("xhigh"),
|
|
16
|
+
Type.Literal("max")
|
|
17
|
+
]))),
|
|
18
|
+
supportsAdaptiveThinking: Type.Optional(Type.Boolean()),
|
|
19
|
+
supportsFastMode: Type.Optional(Type.Boolean()),
|
|
20
|
+
supportsAutoMode: Type.Optional(Type.Boolean())
|
|
21
|
+
});
|
|
22
|
+
function withoutContextSuffix(model) {
|
|
23
|
+
return model.replace(/\[1m\]$/u, "");
|
|
24
|
+
}
|
|
25
|
+
function fromBuiltin(id, builtin) {
|
|
26
|
+
const {
|
|
27
|
+
api: _api,
|
|
28
|
+
provider: _provider,
|
|
29
|
+
baseUrl: _baseUrl,
|
|
30
|
+
compat: _compat,
|
|
31
|
+
...metadata
|
|
32
|
+
} = builtin;
|
|
33
|
+
return {
|
|
34
|
+
...metadata,
|
|
35
|
+
id,
|
|
36
|
+
name: id === "default" ? `Default (${builtin.name})` : builtin.name,
|
|
37
|
+
api: API_ID2,
|
|
38
|
+
provider: PROVIDER_ID2,
|
|
39
|
+
baseUrl: "process://claude-code"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function createClaudeModelCatalog2() {
|
|
43
|
+
const BUILTIN_ANTHROPIC_MODELS = new Map;
|
|
44
|
+
function restoreAnthropicModels(models) {
|
|
45
|
+
BUILTIN_ANTHROPIC_MODELS.clear();
|
|
46
|
+
for (const model of models)
|
|
47
|
+
BUILTIN_ANTHROPIC_MODELS.set(model.id, model);
|
|
48
|
+
}
|
|
49
|
+
const models = new Map;
|
|
50
|
+
const FALLBACK_DISCOVERY = [
|
|
51
|
+
{
|
|
52
|
+
value: "default",
|
|
53
|
+
resolvedModel: "claude-opus-5-5[1m]",
|
|
54
|
+
displayName: "Default (recommended)",
|
|
55
|
+
description: "Opus 5.5 with 1M context · Best for everyday, complex tasks",
|
|
56
|
+
supportsEffort: true,
|
|
57
|
+
supportedEffortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
58
|
+
supportsAdaptiveThinking: true,
|
|
59
|
+
supportsFastMode: true,
|
|
60
|
+
supportsAutoMode: true
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
value: "opus[1m]",
|
|
64
|
+
resolvedModel: "claude-opus-5-5[1m]",
|
|
65
|
+
displayName: "Opus (1M context)",
|
|
66
|
+
description: "Opus 5.5 with 1M context · Best for everyday, complex tasks",
|
|
67
|
+
supportsEffort: true,
|
|
68
|
+
supportedEffortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
69
|
+
supportsAdaptiveThinking: true,
|
|
70
|
+
supportsFastMode: true,
|
|
71
|
+
supportsAutoMode: true
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
value: "claude-fable-5-1[1m]",
|
|
75
|
+
resolvedModel: "claude-fable-5-1",
|
|
76
|
+
displayName: "Fable",
|
|
77
|
+
description: "Fable 5.1 · Most capable for the hardest and longest-running tasks",
|
|
78
|
+
supportsEffort: true,
|
|
79
|
+
supportedEffortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
80
|
+
supportsAdaptiveThinking: true,
|
|
81
|
+
supportsAutoMode: true
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
value: "sonnet",
|
|
85
|
+
resolvedModel: "claude-sonnet-5",
|
|
86
|
+
displayName: "Sonnet",
|
|
87
|
+
description: "Sonnet 5 · Efficient for routine tasks",
|
|
88
|
+
supportsEffort: true,
|
|
89
|
+
supportedEffortLevels: ["low", "medium", "high", "xhigh", "max"],
|
|
90
|
+
supportsAdaptiveThinking: true,
|
|
91
|
+
supportsAutoMode: true
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
value: "haiku",
|
|
95
|
+
resolvedModel: "claude-haiku-4-5-20251001",
|
|
96
|
+
displayName: "Haiku",
|
|
97
|
+
description: "Haiku 4.5 · Fastest for quick answers"
|
|
98
|
+
}
|
|
99
|
+
];
|
|
100
|
+
const ALIASES = {
|
|
101
|
+
sonnet: "claude-sonnet-5",
|
|
102
|
+
haiku: "claude-haiku-4-5-20251001",
|
|
103
|
+
"claude-haiku-4-5": "claude-haiku-4-5-20251001",
|
|
104
|
+
opus: "claude-opus-5-5",
|
|
105
|
+
fable: "claude-fable-5-1"
|
|
106
|
+
};
|
|
107
|
+
function canonicalModelId(info) {
|
|
108
|
+
if (info.value === "default")
|
|
109
|
+
return "default";
|
|
110
|
+
return withoutContextSuffix(info.resolvedModel || ALIASES[withoutContextSuffix(info.value)] || info.value);
|
|
111
|
+
}
|
|
112
|
+
function fallbackModel(info, id) {
|
|
113
|
+
const supported = info.supportedEffortLevels;
|
|
114
|
+
const thinkingLevelMap = supported?.length ? Object.fromEntries(supported.map((level) => [level, level])) : undefined;
|
|
115
|
+
return {
|
|
116
|
+
id,
|
|
117
|
+
name: `${info.displayName} (Claude Code)`,
|
|
118
|
+
api: API_ID2,
|
|
119
|
+
provider: PROVIDER_ID2,
|
|
120
|
+
baseUrl: "process://claude-code",
|
|
121
|
+
reasoning: info.supportsAdaptiveThinking === true || info.supportsEffort === true,
|
|
122
|
+
...thinkingLevelMap ? { thinkingLevelMap } : {},
|
|
123
|
+
input: ["text", "image"],
|
|
124
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
125
|
+
contextWindow: /\b1M context\b/iu.test(info.description) ? 1e6 : 200000,
|
|
126
|
+
maxTokens: 64000
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function modelFromDiscovery(info) {
|
|
130
|
+
const id = canonicalModelId(info);
|
|
131
|
+
const builtinId = id === "default" ? withoutContextSuffix(info.resolvedModel || "") : id;
|
|
132
|
+
const builtin = BUILTIN_ANTHROPIC_MODELS.get(builtinId);
|
|
133
|
+
models.set(id, {
|
|
134
|
+
route: info.value,
|
|
135
|
+
nativeToolChanges: builtin?.compat?.supportsMidConvoToolChanges === true,
|
|
136
|
+
adaptiveThinking: info.supportsAdaptiveThinking === true
|
|
137
|
+
});
|
|
138
|
+
return builtin ? fromBuiltin(id, builtin) : fallbackModel(info, id);
|
|
139
|
+
}
|
|
140
|
+
function fallbackClaudeModels() {
|
|
141
|
+
return FALLBACK_DISCOVERY.map(modelFromDiscovery);
|
|
142
|
+
}
|
|
143
|
+
function nativeModel(model) {
|
|
144
|
+
const direct = models.get(model)?.route;
|
|
145
|
+
if (direct)
|
|
146
|
+
return direct;
|
|
147
|
+
if (model === "default")
|
|
148
|
+
return model;
|
|
149
|
+
const canonical = canonicalRoute(model);
|
|
150
|
+
return model.endsWith("[1m]") ? `${canonical}[1m]` : canonical;
|
|
151
|
+
}
|
|
152
|
+
function canonicalRoute(model) {
|
|
153
|
+
const withoutSuffix = withoutContextSuffix(model);
|
|
154
|
+
return ALIASES[withoutSuffix] ?? withoutSuffix;
|
|
155
|
+
}
|
|
156
|
+
function supportsAdaptiveThinking(model) {
|
|
157
|
+
const canonical = canonicalRoute(model);
|
|
158
|
+
return models.get(model)?.adaptiveThinking ?? models.get(canonical)?.adaptiveThinking ?? true;
|
|
159
|
+
}
|
|
160
|
+
function supportsNativeToolChanges(model) {
|
|
161
|
+
const canonical = canonicalRoute(model);
|
|
162
|
+
return models.get(model)?.nativeToolChanges ?? models.get(canonical)?.nativeToolChanges ?? BUILTIN_ANTHROPIC_MODELS.get(canonical)?.compat?.supportsMidConvoToolChanges === true;
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
restoreAnthropicModels,
|
|
166
|
+
modelFromDiscovery,
|
|
167
|
+
fallbackClaudeModels,
|
|
168
|
+
nativeModel,
|
|
169
|
+
supportsAdaptiveThinking,
|
|
170
|
+
supportsNativeToolChanges
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export { PROVIDER_ID2, API_ID2, discoveredModelSchema2, createClaudeModelCatalog2 };
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import {
|
|
2
|
+
matchesRecord2,
|
|
3
|
+
parseRecord2,
|
|
4
|
+
parseStreamEvent2
|
|
5
|
+
} from "./billing-0y7dfkp2.js";
|
|
6
|
+
|
|
7
|
+
// examples/claude-code/turn-relay.ts
|
|
8
|
+
import { createServer, request as httpRequest } from "node:http";
|
|
9
|
+
import https from "node:https";
|
|
10
|
+
import { randomBytes } from "node:crypto";
|
|
11
|
+
class AnthropicCapture2 {
|
|
12
|
+
message;
|
|
13
|
+
complete = false;
|
|
14
|
+
error;
|
|
15
|
+
pending = "";
|
|
16
|
+
decoder = new TextDecoder;
|
|
17
|
+
argumentsByIndex = new Map;
|
|
18
|
+
onEvent;
|
|
19
|
+
constructor(onEvent) {
|
|
20
|
+
this.onEvent = onEvent;
|
|
21
|
+
}
|
|
22
|
+
feed(chunk) {
|
|
23
|
+
this.pending += this.decoder.decode(chunk, { stream: true });
|
|
24
|
+
this.consumeFrames();
|
|
25
|
+
}
|
|
26
|
+
finish() {
|
|
27
|
+
this.pending += this.decoder.decode();
|
|
28
|
+
this.consumeFrames();
|
|
29
|
+
}
|
|
30
|
+
consumeFrames() {
|
|
31
|
+
for (;; ) {
|
|
32
|
+
const match = /\r?\n\r?\n/u.exec(this.pending);
|
|
33
|
+
if (!match)
|
|
34
|
+
return;
|
|
35
|
+
const frame = this.pending.slice(0, match.index);
|
|
36
|
+
this.pending = this.pending.slice(match.index + match[0].length);
|
|
37
|
+
const data = frame.split(/\r?\n/u).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join(`
|
|
38
|
+
`);
|
|
39
|
+
if (!data || data === "[DONE]")
|
|
40
|
+
continue;
|
|
41
|
+
const event = parseStreamEvent2(data);
|
|
42
|
+
if (event === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
this.apply(event);
|
|
45
|
+
this.onEvent?.(event);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
apply(event) {
|
|
49
|
+
switch (event.type) {
|
|
50
|
+
case "message_start":
|
|
51
|
+
this.message = structuredClone(event.message);
|
|
52
|
+
break;
|
|
53
|
+
case "content_block_start":
|
|
54
|
+
if (this.message)
|
|
55
|
+
this.message.content[event.index] = structuredClone(event.content_block);
|
|
56
|
+
break;
|
|
57
|
+
case "content_block_delta": {
|
|
58
|
+
const block = this.message?.content[event.index];
|
|
59
|
+
if (!block)
|
|
60
|
+
break;
|
|
61
|
+
const delta = event.delta;
|
|
62
|
+
if (delta.type === "text_delta" && block.type === "text")
|
|
63
|
+
block.text += delta.text;
|
|
64
|
+
else if (delta.type === "thinking_delta" && block.type === "thinking")
|
|
65
|
+
block.thinking += delta.thinking;
|
|
66
|
+
else if (delta.type === "signature_delta" && block.type === "thinking") {
|
|
67
|
+
block.signature = (block.signature ?? "") + delta.signature;
|
|
68
|
+
} else if (delta.type === "input_json_delta" && block.type === "tool_use") {
|
|
69
|
+
this.argumentsByIndex.set(event.index, (this.argumentsByIndex.get(event.index) ?? "") + delta.partial_json);
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case "content_block_stop": {
|
|
74
|
+
const block = this.message?.content[event.index];
|
|
75
|
+
if (block?.type === "tool_use" && this.argumentsByIndex.has(event.index)) {
|
|
76
|
+
const raw = this.argumentsByIndex.get(event.index) ?? "";
|
|
77
|
+
block.input = raw.trim() ? parseRecord2(raw) : {};
|
|
78
|
+
this.argumentsByIndex.delete(event.index);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case "message_delta":
|
|
83
|
+
if (this.message) {
|
|
84
|
+
Object.assign(this.message, event.delta);
|
|
85
|
+
if (event.usage)
|
|
86
|
+
this.message.usage = {
|
|
87
|
+
input_tokens: this.message.usage?.input_tokens ?? 0,
|
|
88
|
+
output_tokens: this.message.usage?.output_tokens ?? 0,
|
|
89
|
+
...this.message.usage,
|
|
90
|
+
...event.usage
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
case "message_stop":
|
|
95
|
+
this.complete = !!this.message?.stop_reason && this.argumentsByIndex.size === 0;
|
|
96
|
+
break;
|
|
97
|
+
case "error": {
|
|
98
|
+
const { type, message } = event.error ?? {};
|
|
99
|
+
this.error = [type, message].filter(Boolean).join(": ") || "unknown stream error";
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
var STRIPPED_REQUEST_HEADERS = new Set([
|
|
106
|
+
"host",
|
|
107
|
+
"connection",
|
|
108
|
+
"content-length",
|
|
109
|
+
"transfer-encoding",
|
|
110
|
+
"proxy-authorization",
|
|
111
|
+
"proxy-connection",
|
|
112
|
+
"accept-encoding"
|
|
113
|
+
]);
|
|
114
|
+
var STRIPPED_RESPONSE_HEADERS = new Set(["connection", "transfer-encoding", "server", "date"]);
|
|
115
|
+
|
|
116
|
+
class TurnRelay2 {
|
|
117
|
+
capture;
|
|
118
|
+
used = false;
|
|
119
|
+
denied = 0;
|
|
120
|
+
status;
|
|
121
|
+
requestId;
|
|
122
|
+
failure;
|
|
123
|
+
errorBody = Buffer.alloc(0);
|
|
124
|
+
originalRequestBody;
|
|
125
|
+
requestBody;
|
|
126
|
+
upstream;
|
|
127
|
+
prefix = `/turn/${randomBytes(32).toString("base64url")}`;
|
|
128
|
+
server;
|
|
129
|
+
sockets = new Set;
|
|
130
|
+
active = new Set;
|
|
131
|
+
cancelled = false;
|
|
132
|
+
closed = false;
|
|
133
|
+
baseUrl;
|
|
134
|
+
transformRequest;
|
|
135
|
+
requestBetas = [];
|
|
136
|
+
timeoutMs;
|
|
137
|
+
constructor(upstream = "https://api.anthropic.com", timeoutMs = 180000, onEvent, transformRequest, requestBetas = []) {
|
|
138
|
+
this.timeoutMs = timeoutMs;
|
|
139
|
+
this.upstream = new URL(upstream);
|
|
140
|
+
const loopback = ["127.0.0.1", "::1", "localhost"].includes(this.upstream.hostname);
|
|
141
|
+
if (this.upstream.protocol !== "https:" && !(this.upstream.protocol === "http:" && loopback) || this.upstream.username || this.upstream.password || this.upstream.search || this.upstream.hash) {
|
|
142
|
+
throw new Error("Claude Code upstream must be HTTPS or a loopback HTTP fixture");
|
|
143
|
+
}
|
|
144
|
+
this.capture = new AnthropicCapture2(onEvent);
|
|
145
|
+
this.transformRequest = transformRequest;
|
|
146
|
+
this.requestBetas = requestBetas;
|
|
147
|
+
this.server = createServer((request, response) => {
|
|
148
|
+
const operation = this.handle(request, response).finally(() => this.active.delete(operation));
|
|
149
|
+
this.active.add(operation);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
beginTurn(onEvent, transformRequest, requestBetas = []) {
|
|
153
|
+
if (this.closed)
|
|
154
|
+
throw new Error("Claude Code turn relay is closed");
|
|
155
|
+
if (this.active.size > 0)
|
|
156
|
+
throw new Error("Claude Code turn relay still has an active request");
|
|
157
|
+
this.capture = new AnthropicCapture2(onEvent);
|
|
158
|
+
this.used = false;
|
|
159
|
+
this.denied = 0;
|
|
160
|
+
this.status = undefined;
|
|
161
|
+
this.requestId = undefined;
|
|
162
|
+
this.failure = undefined;
|
|
163
|
+
this.errorBody = Buffer.alloc(0);
|
|
164
|
+
this.originalRequestBody = undefined;
|
|
165
|
+
this.requestBody = undefined;
|
|
166
|
+
this.transformRequest = transformRequest;
|
|
167
|
+
this.requestBetas = requestBetas;
|
|
168
|
+
this.cancelled = false;
|
|
169
|
+
}
|
|
170
|
+
finishTurn() {
|
|
171
|
+
if (this.cancelled)
|
|
172
|
+
return;
|
|
173
|
+
this.cancelled = true;
|
|
174
|
+
for (const socket of this.sockets)
|
|
175
|
+
socket.destroy(new Error("Claude Code request aborted"));
|
|
176
|
+
this.sockets.clear();
|
|
177
|
+
this.server.closeAllConnections?.();
|
|
178
|
+
}
|
|
179
|
+
async start() {
|
|
180
|
+
await new Promise((resolve, reject) => {
|
|
181
|
+
const onError = (error) => reject(error);
|
|
182
|
+
this.server.once("error", onError);
|
|
183
|
+
this.server.listen(0, "127.0.0.1", () => {
|
|
184
|
+
this.server.off("error", onError);
|
|
185
|
+
resolve();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
const address = this.server.address();
|
|
189
|
+
if (address === null || typeof address === "string")
|
|
190
|
+
throw new Error("Claude Code relay did not bind a TCP port");
|
|
191
|
+
this.baseUrl = `http://127.0.0.1:${address.port}${this.prefix}`;
|
|
192
|
+
return this.baseUrl;
|
|
193
|
+
}
|
|
194
|
+
errorText() {
|
|
195
|
+
if (this.capture.error)
|
|
196
|
+
return this.capture.error;
|
|
197
|
+
const text = this.errorBody.toString("utf8");
|
|
198
|
+
try {
|
|
199
|
+
const payload = parseRecord2(text);
|
|
200
|
+
return matchesRecord2(payload.error) && typeof payload.error.message === "string" ? payload.error.message : text;
|
|
201
|
+
} catch {
|
|
202
|
+
return text;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
abort() {
|
|
206
|
+
this.finishTurn();
|
|
207
|
+
}
|
|
208
|
+
async settled() {
|
|
209
|
+
await Promise.allSettled(this.active);
|
|
210
|
+
}
|
|
211
|
+
async close() {
|
|
212
|
+
this.closed = true;
|
|
213
|
+
this.abort();
|
|
214
|
+
await new Promise((resolve) => {
|
|
215
|
+
if (!this.server.listening)
|
|
216
|
+
return resolve();
|
|
217
|
+
this.server.close(() => resolve());
|
|
218
|
+
});
|
|
219
|
+
await this.settled();
|
|
220
|
+
}
|
|
221
|
+
async handle(request, response) {
|
|
222
|
+
const requestUrl = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
223
|
+
if (request.method !== "POST" || requestUrl.pathname !== `${this.prefix}/v1/messages` || request.headers.origin) {
|
|
224
|
+
response.writeHead(404).end();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (this.cancelled || this.used) {
|
|
228
|
+
this.denied += 1;
|
|
229
|
+
const body = Buffer.from(JSON.stringify({
|
|
230
|
+
type: "error",
|
|
231
|
+
error: { type: "invalid_request_error", message: "NYTE_CLAUDE_CODE_TURN_RELAY_CONSUMED" }
|
|
232
|
+
}));
|
|
233
|
+
response.writeHead(400, { "content-type": "application/json", "content-length": body.length }).end(body);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.used = true;
|
|
237
|
+
this.sockets.add(request);
|
|
238
|
+
this.sockets.add(response);
|
|
239
|
+
let upstreamRequest;
|
|
240
|
+
try {
|
|
241
|
+
let body = await this.readBody(request);
|
|
242
|
+
this.originalRequestBody = Buffer.from(body);
|
|
243
|
+
if (this.transformRequest)
|
|
244
|
+
body = this.transformRequest(body);
|
|
245
|
+
this.requestBody = Buffer.from(body);
|
|
246
|
+
if (this.cancelled)
|
|
247
|
+
return;
|
|
248
|
+
const headers = {};
|
|
249
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
250
|
+
if (!STRIPPED_REQUEST_HEADERS.has(name.toLowerCase()) && value !== undefined)
|
|
251
|
+
headers[name] = value;
|
|
252
|
+
}
|
|
253
|
+
headers["accept-encoding"] = "identity";
|
|
254
|
+
headers["content-length"] = body.length;
|
|
255
|
+
if (this.requestBetas.length > 0) {
|
|
256
|
+
const existing = Array.isArray(headers["anthropic-beta"]) ? headers["anthropic-beta"].join(",") : String(headers["anthropic-beta"] ?? "");
|
|
257
|
+
headers["anthropic-beta"] = [
|
|
258
|
+
...new Set([
|
|
259
|
+
...existing.split(",").map((value) => value.trim()).filter(Boolean),
|
|
260
|
+
...this.requestBetas
|
|
261
|
+
])
|
|
262
|
+
].join(",");
|
|
263
|
+
}
|
|
264
|
+
const pathPrefix = this.upstream.pathname.replace(/\/$/u, "");
|
|
265
|
+
const send = this.upstream.protocol === "https:" ? https.request : httpRequest;
|
|
266
|
+
const outgoing = send({
|
|
267
|
+
protocol: this.upstream.protocol,
|
|
268
|
+
hostname: this.upstream.hostname,
|
|
269
|
+
port: this.upstream.port || undefined,
|
|
270
|
+
method: "POST",
|
|
271
|
+
path: `${pathPrefix}/v1/messages${requestUrl.search}`,
|
|
272
|
+
headers,
|
|
273
|
+
timeout: this.timeoutMs
|
|
274
|
+
});
|
|
275
|
+
upstreamRequest = outgoing;
|
|
276
|
+
this.sockets.add(outgoing);
|
|
277
|
+
const upstreamResponse = await new Promise((resolve, reject) => {
|
|
278
|
+
outgoing.once("response", resolve);
|
|
279
|
+
outgoing.once("error", reject);
|
|
280
|
+
outgoing.end(body);
|
|
281
|
+
});
|
|
282
|
+
this.sockets.add(upstreamResponse);
|
|
283
|
+
this.status = upstreamResponse.statusCode;
|
|
284
|
+
this.requestId = String(upstreamResponse.headers["request-id"] ?? upstreamResponse.headers["x-request-id"] ?? "") || undefined;
|
|
285
|
+
const responseHeaders = {};
|
|
286
|
+
for (const [name, value] of Object.entries(upstreamResponse.headers)) {
|
|
287
|
+
if (!STRIPPED_RESPONSE_HEADERS.has(name.toLowerCase()) && value !== undefined)
|
|
288
|
+
responseHeaders[name] = value;
|
|
289
|
+
}
|
|
290
|
+
responseHeaders.connection = "close";
|
|
291
|
+
response.writeHead(upstreamResponse.statusCode ?? 502, responseHeaders);
|
|
292
|
+
for await (const rawChunk of upstreamResponse) {
|
|
293
|
+
const chunk = Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk);
|
|
294
|
+
if (this.status === 200)
|
|
295
|
+
this.capture.feed(chunk);
|
|
296
|
+
else if (this.errorBody.length < 65536) {
|
|
297
|
+
this.errorBody = Buffer.concat([this.errorBody, chunk]).subarray(0, 65536);
|
|
298
|
+
}
|
|
299
|
+
if (!response.write(chunk))
|
|
300
|
+
await this.waitForDrain(response);
|
|
301
|
+
}
|
|
302
|
+
if (this.status === 200)
|
|
303
|
+
this.capture.finish();
|
|
304
|
+
response.end();
|
|
305
|
+
} catch (error) {
|
|
306
|
+
this.failure = error instanceof Error ? error.name : "UnknownError";
|
|
307
|
+
if (!response.headersSent)
|
|
308
|
+
response.writeHead(this.cancelled ? 499 : 502).end();
|
|
309
|
+
else
|
|
310
|
+
response.destroy();
|
|
311
|
+
} finally {
|
|
312
|
+
if (upstreamRequest)
|
|
313
|
+
this.sockets.delete(upstreamRequest);
|
|
314
|
+
this.sockets.delete(request);
|
|
315
|
+
this.sockets.delete(response);
|
|
316
|
+
upstreamRequest?.destroy();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async waitForDrain(response) {
|
|
320
|
+
await new Promise((resolve, reject) => {
|
|
321
|
+
const cleanup = () => {
|
|
322
|
+
response.off("drain", onDrain);
|
|
323
|
+
response.off("close", onClose);
|
|
324
|
+
response.off("error", onError);
|
|
325
|
+
};
|
|
326
|
+
const onDrain = () => {
|
|
327
|
+
cleanup();
|
|
328
|
+
resolve();
|
|
329
|
+
};
|
|
330
|
+
const onClose = () => {
|
|
331
|
+
cleanup();
|
|
332
|
+
reject(new Error("Claude Code disconnected from the turn relay"));
|
|
333
|
+
};
|
|
334
|
+
const onError = (error) => {
|
|
335
|
+
cleanup();
|
|
336
|
+
reject(error);
|
|
337
|
+
};
|
|
338
|
+
response.once("drain", onDrain);
|
|
339
|
+
response.once("close", onClose);
|
|
340
|
+
response.once("error", onError);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async readBody(request) {
|
|
344
|
+
const chunks = [];
|
|
345
|
+
for await (const rawChunk of request)
|
|
346
|
+
chunks.push(Buffer.isBuffer(rawChunk) ? rawChunk : Buffer.from(rawChunk));
|
|
347
|
+
return Buffer.concat(chunks);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export { AnthropicCapture2, TurnRelay2 };
|