@pablopunk/tau 0.0.0-stage → 0.1.1
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/auth-storage-Dk3CUY3q.js +1814 -0
- package/dist/bin.js +23 -0
- package/dist/cli-entry-DcDpQ30L.js +13 -0
- package/dist/client-C91-b2CL.js +1 -0
- package/dist/client-y2WkNuZj.js +7977 -0
- package/dist/dist-BFP19NOO.js +432 -0
- package/dist/dist-Bh1bQHZA.js +38890 -0
- package/dist/dist-Cq73_r65.js +3 -0
- package/dist/esm-CMGh_WhC.js +9446 -0
- package/dist/execute-BeGXhU1O.js +1757 -0
- package/dist/fake-claude-connectors-DSP9UC8d.js +1 -0
- package/dist/fake-claude-connectors-sDLeP10T.js +107 -0
- package/dist/fake-claude-sdk-CvqW_1iQ.js +571 -0
- package/dist/fake-claude-thread-script-2iHHwUZM.js +48 -0
- package/dist/last-exit-write-ksHeHGrC.js +9 -0
- package/dist/latest-version-di9hD7cY.js +106 -0
- package/dist/lib-DIX7q_HJ.js +1460 -0
- package/dist/machines-behind-BjGgmSs_.js +257 -0
- package/dist/main-D-Tg9b5Y.js +1610 -0
- package/dist/manager-B_WHbb1U.js +589 -0
- package/dist/names-cTrEXB6w.js +35 -0
- package/dist/node-BpwblPFP.js +56269 -0
- package/dist/pdfjs-nTcHjDPT.js +69856 -0
- package/dist/photon_rs-DBr7W_rK.js +4252 -0
- package/dist/promise.try-CAAOYvMP.js +1426 -0
- package/dist/readability-DsDNO63a.js +1670 -0
- package/dist/real-deps-f96UmLBz.js +89 -0
- package/dist/rolldown-runtime-1p2KPIdQ.js +44 -0
- package/dist/runtime-i-oEtgAl.js +2721 -0
- package/dist/sdk-missing-CHbVbY1G.js +10 -0
- package/dist/sdk-port-BGw9ORUR.js +215 -0
- package/dist/serve-Cmy0rUWr.js +2932 -0
- package/dist/serve-Dmzn5V0l.js +1 -0
- package/dist/service-command-C74K0saj.js +34 -0
- package/dist/service-command-DqcstJ8e.js +1 -0
- package/dist/shell-CQa4e7rx.js +643 -0
- package/dist/skills/builtin/tau/SKILL.md +51 -0
- package/dist/src-DO9LUlhi.js +44088 -0
- package/dist/streamableHttp-BBaRYV9h.js +1 -0
- package/dist/streamableHttp-DPAoMlJd.js +1931 -0
- package/dist/tool-DBZK2lci.js +14335 -0
- package/dist/tool-server-BjP37EJg.js +40 -0
- package/dist/turndown.cjs-Bo1xLg2S.js +15620 -0
- package/dist/turndown.cjs-jpUF50AR.js +3 -0
- package/dist/types-C4M3CRp2.js +7592 -0
- package/dist/undici-8cI97lnP.js +27110 -0
- package/dist/wait-for-lock-B1Qx8GWg.js +1 -0
- package/dist/wait-for-lock-CrSl2mi1.js +25 -0
- package/dist/watchdog.js +47 -0
- package/dist/web/dist/assets/addon-fit-DIOBYJe3.js +1 -0
- package/dist/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/web/dist/assets/index-BktLKZHR.js +108 -0
- package/dist/web/dist/assets/index-D_Aa3Oou.css +2 -0
- package/dist/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/dist/assets/wght-DMLdVLkb.css +1 -0
- package/dist/web/dist/assets/xterm-BrP-ENHg.css +1 -0
- package/dist/web/dist/assets/xterm-CerOhKYV.js +16 -0
- package/dist/web/dist/favicon.svg +14 -0
- package/dist/web/dist/index.html +25 -0
- package/package.json +53 -4
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { t as FAKE_LINEAR, n as FAKE_NEEDS_AUTH, r as FAKE_SLACK } from "./fake-claude-connectors-sDLeP10T.js";
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { i as connectorServerOf, n as cliToolNameOf } from "./names-cTrEXB6w.js";
|
|
2
|
+
//#region ../core/src/claude-cli/testing/fake-claude-connectors.ts
|
|
3
|
+
const FAKE_LINEAR = {
|
|
4
|
+
name: "claude.ai Linear",
|
|
5
|
+
status: "connected",
|
|
6
|
+
tools: [
|
|
7
|
+
{
|
|
8
|
+
name: "list_teams",
|
|
9
|
+
readOnly: true
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "get_issue",
|
|
13
|
+
readOnly: true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "save_comment",
|
|
17
|
+
readOnly: false
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
};
|
|
21
|
+
const FAKE_SLACK = {
|
|
22
|
+
name: "claude.ai Slack",
|
|
23
|
+
status: "connected",
|
|
24
|
+
tools: [{
|
|
25
|
+
name: "slack_search_public",
|
|
26
|
+
readOnly: true
|
|
27
|
+
}, {
|
|
28
|
+
name: "slack_send_message",
|
|
29
|
+
readOnly: false
|
|
30
|
+
}]
|
|
31
|
+
};
|
|
32
|
+
const FAKE_NEEDS_AUTH = {
|
|
33
|
+
name: "claude.ai Notion",
|
|
34
|
+
status: "needs-auth",
|
|
35
|
+
tools: []
|
|
36
|
+
};
|
|
37
|
+
const connectorCliName = (server, tool) => cliToolNameOf({
|
|
38
|
+
server: connectorServerOf(server),
|
|
39
|
+
tool
|
|
40
|
+
});
|
|
41
|
+
const connectorsAreOpen = (options) => !options.strictMcpConfig && !options.settings.disableClaudeAiConnectors;
|
|
42
|
+
const matchesAllowed = (allowed, toolName) => allowed.endsWith("*") ? toolName.startsWith(allowed.slice(0, -1)) : allowed === toolName;
|
|
43
|
+
const isAllowedByList = (allowedTools, toolName) => allowedTools.some((allowed) => matchesAllowed(allowed, toolName));
|
|
44
|
+
const disabledServer = (server) => ({
|
|
45
|
+
...server,
|
|
46
|
+
status: "disabled",
|
|
47
|
+
tools: []
|
|
48
|
+
});
|
|
49
|
+
function createFakeMcpState(input) {
|
|
50
|
+
const switchedOff = /* @__PURE__ */ new Set();
|
|
51
|
+
const { toggles } = input;
|
|
52
|
+
let polls = 0;
|
|
53
|
+
const stillPending = () => polls >= 1 && polls <= input.pendingPolls;
|
|
54
|
+
const tauServer = () => ({
|
|
55
|
+
name: "tau",
|
|
56
|
+
status: "connected",
|
|
57
|
+
source: "sdk",
|
|
58
|
+
error: null,
|
|
59
|
+
tools: input.tauTools().map((name) => ({
|
|
60
|
+
name,
|
|
61
|
+
readOnly: false
|
|
62
|
+
}))
|
|
63
|
+
});
|
|
64
|
+
const connectorServer = (server) => {
|
|
65
|
+
const status = {
|
|
66
|
+
name: server.name,
|
|
67
|
+
status: stillPending() ? "pending" : server.status,
|
|
68
|
+
source: server.source ?? "claudeai",
|
|
69
|
+
error: null,
|
|
70
|
+
tools: stillPending() ? [] : server.tools
|
|
71
|
+
};
|
|
72
|
+
return switchedOff.has(server.name) ? disabledServer(status) : status;
|
|
73
|
+
};
|
|
74
|
+
const statuses = () => [tauServer(), ...input.open ? input.connectors.map(connectorServer) : []];
|
|
75
|
+
const loadedConnectorTools = () => statuses().filter((server) => server.source !== "sdk" && server.status === "connected").flatMap((server) => server.tools.map((tool) => ({
|
|
76
|
+
server: server.name,
|
|
77
|
+
tool: tool.name
|
|
78
|
+
})));
|
|
79
|
+
return {
|
|
80
|
+
toggles,
|
|
81
|
+
control: {
|
|
82
|
+
async mcpServerStatus() {
|
|
83
|
+
polls += 1;
|
|
84
|
+
return statuses();
|
|
85
|
+
},
|
|
86
|
+
async toggleMcpServer(name, enabled) {
|
|
87
|
+
toggles.push({
|
|
88
|
+
name,
|
|
89
|
+
enabled
|
|
90
|
+
});
|
|
91
|
+
if (enabled) switchedOff.delete(name);
|
|
92
|
+
else switchedOff.add(name);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
context: () => {
|
|
96
|
+
const loadedMcpToolCounts = {};
|
|
97
|
+
for (const { server } of loadedConnectorTools()) loadedMcpToolCounts[server] = (loadedMcpToolCounts[server] ?? 0) + 1;
|
|
98
|
+
return {
|
|
99
|
+
memoryFileCount: 0,
|
|
100
|
+
loadedMcpToolCounts
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
activeToolNames: () => loadedConnectorTools().map(({ server, tool }) => connectorCliName(server, tool))
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//#endregion
|
|
107
|
+
export { connectorsAreOpen as a, connectorCliName as i, FAKE_NEEDS_AUTH as n, createFakeMcpState as o, FAKE_SLACK as r, isAllowedByList as s, FAKE_LINEAR as t };
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import { t as TOOL_NAME_PREFIX } from "./tool-server-BjP37EJg.js";
|
|
2
|
+
import { a as connectorsAreOpen, i as connectorCliName, o as createFakeMcpState, s as isAllowedByList } from "./fake-claude-connectors-sDLeP10T.js";
|
|
3
|
+
//#region ../core/src/claude-cli/testing/fake-claude-events.ts
|
|
4
|
+
const FAKE_USAGE = {
|
|
5
|
+
input_tokens: 10,
|
|
6
|
+
output_tokens: 5,
|
|
7
|
+
cache_read_input_tokens: 0,
|
|
8
|
+
cache_creation_input_tokens: 0
|
|
9
|
+
};
|
|
10
|
+
const FAKE_CONTEXT_WINDOW = 1e6;
|
|
11
|
+
function createMessageIds() {
|
|
12
|
+
let message = 0;
|
|
13
|
+
let uuid = 0;
|
|
14
|
+
let tool = 0;
|
|
15
|
+
return {
|
|
16
|
+
messageId: () => `msg_fake_${++message}`,
|
|
17
|
+
uuid: () => `uuid-fake-${++uuid}`,
|
|
18
|
+
toolUseId: () => `toolu_fake_${++tool}`
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const streamEvent = (sessionId, event) => ({
|
|
22
|
+
type: "stream_event",
|
|
23
|
+
session_id: sessionId,
|
|
24
|
+
event
|
|
25
|
+
});
|
|
26
|
+
const deltaOf = (block) => {
|
|
27
|
+
if (block.type === "text") return {
|
|
28
|
+
type: "text_delta",
|
|
29
|
+
text: block.text
|
|
30
|
+
};
|
|
31
|
+
if (block.type === "thinking") return {
|
|
32
|
+
type: "thinking_delta",
|
|
33
|
+
thinking: block.thinking
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
type: "input_json_delta",
|
|
37
|
+
partial_json: JSON.stringify(block.input)
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const startOf = (block) => block.type === "tool_use" ? {
|
|
41
|
+
...block,
|
|
42
|
+
input: {}
|
|
43
|
+
} : {
|
|
44
|
+
...block,
|
|
45
|
+
text: "",
|
|
46
|
+
thinking: ""
|
|
47
|
+
};
|
|
48
|
+
/** Stream events, then one `assistant` message per content block, as the SDK emits them. */
|
|
49
|
+
function apiMessageEvents(sessionId, message, ids) {
|
|
50
|
+
const events = [streamEvent(sessionId, {
|
|
51
|
+
type: "message_start",
|
|
52
|
+
message: {
|
|
53
|
+
id: message.id,
|
|
54
|
+
model: message.model,
|
|
55
|
+
usage: FAKE_USAGE
|
|
56
|
+
}
|
|
57
|
+
})];
|
|
58
|
+
message.blocks.forEach((block, index) => {
|
|
59
|
+
events.push(streamEvent(sessionId, {
|
|
60
|
+
type: "content_block_start",
|
|
61
|
+
index,
|
|
62
|
+
content_block: startOf(block)
|
|
63
|
+
}));
|
|
64
|
+
events.push(streamEvent(sessionId, {
|
|
65
|
+
type: "content_block_delta",
|
|
66
|
+
index,
|
|
67
|
+
delta: deltaOf(block)
|
|
68
|
+
}));
|
|
69
|
+
events.push(streamEvent(sessionId, {
|
|
70
|
+
type: "content_block_stop",
|
|
71
|
+
index
|
|
72
|
+
}));
|
|
73
|
+
});
|
|
74
|
+
events.push(streamEvent(sessionId, {
|
|
75
|
+
type: "message_delta",
|
|
76
|
+
delta: { stop_reason: message.stopReason },
|
|
77
|
+
usage: FAKE_USAGE
|
|
78
|
+
}), streamEvent(sessionId, { type: "message_stop" }));
|
|
79
|
+
for (const block of message.blocks) events.push({
|
|
80
|
+
type: "assistant",
|
|
81
|
+
uuid: ids.uuid(),
|
|
82
|
+
session_id: sessionId,
|
|
83
|
+
...message.error && { error: message.error },
|
|
84
|
+
message: {
|
|
85
|
+
id: message.id,
|
|
86
|
+
model: message.model,
|
|
87
|
+
role: "assistant",
|
|
88
|
+
content: [block],
|
|
89
|
+
stop_reason: message.stopReason,
|
|
90
|
+
usage: FAKE_USAGE
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return events;
|
|
94
|
+
}
|
|
95
|
+
const rateLimitEvent = (sessionId, limit, uuid) => ({
|
|
96
|
+
type: "rate_limit_event",
|
|
97
|
+
uuid,
|
|
98
|
+
session_id: sessionId,
|
|
99
|
+
rate_limit_info: {
|
|
100
|
+
status: limit.status,
|
|
101
|
+
rateLimitType: "five_hour",
|
|
102
|
+
utilization: limit.fiveHour,
|
|
103
|
+
resetsAt: limit.resetsAt,
|
|
104
|
+
unifiedWindows: { five_hour: { utilization: limit.fiveHour } }
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const toolResultEvent = (sessionId, toolUseId, text, isError, uuid) => ({
|
|
108
|
+
type: "user",
|
|
109
|
+
uuid,
|
|
110
|
+
session_id: sessionId,
|
|
111
|
+
message: {
|
|
112
|
+
role: "user",
|
|
113
|
+
content: [{
|
|
114
|
+
type: "tool_result",
|
|
115
|
+
tool_use_id: toolUseId,
|
|
116
|
+
content: text,
|
|
117
|
+
is_error: isError
|
|
118
|
+
}]
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const resultEvent = (sessionId, model, outcome, uuid) => ({
|
|
122
|
+
type: "result",
|
|
123
|
+
uuid,
|
|
124
|
+
session_id: sessionId,
|
|
125
|
+
subtype: outcome === "success" ? "success" : "error_during_execution",
|
|
126
|
+
is_error: outcome !== "success",
|
|
127
|
+
...outcome === "interrupted" && { terminal_reason: "aborted_tools" },
|
|
128
|
+
usage: FAKE_USAGE,
|
|
129
|
+
modelUsage: { [model]: {
|
|
130
|
+
...FAKE_USAGE,
|
|
131
|
+
contextWindow: FAKE_CONTEXT_WINDOW,
|
|
132
|
+
costUSD: 0
|
|
133
|
+
} }
|
|
134
|
+
});
|
|
135
|
+
const initEvent = (sessionId, model, toolNames, apiKeySource) => ({
|
|
136
|
+
type: "system",
|
|
137
|
+
subtype: "init",
|
|
138
|
+
session_id: sessionId,
|
|
139
|
+
model,
|
|
140
|
+
tools: toolNames,
|
|
141
|
+
mcp_servers: [{
|
|
142
|
+
name: "tau",
|
|
143
|
+
status: "connected"
|
|
144
|
+
}],
|
|
145
|
+
apiKeySource,
|
|
146
|
+
claude_code_version: "0.0.0-fake"
|
|
147
|
+
});
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region ../core/src/claude-cli/testing/fake-claude-models.ts
|
|
150
|
+
const STANDARD_EFFORT = [
|
|
151
|
+
"low",
|
|
152
|
+
"medium",
|
|
153
|
+
"high",
|
|
154
|
+
"xhigh"
|
|
155
|
+
];
|
|
156
|
+
const NO_EFFORT = [];
|
|
157
|
+
const row = (value, resolvedModel, displayName, supportedEffortLevels = STANDARD_EFFORT) => ({
|
|
158
|
+
value,
|
|
159
|
+
resolvedModel,
|
|
160
|
+
displayName,
|
|
161
|
+
description: `${displayName} (fake)`,
|
|
162
|
+
supportedEffortLevels,
|
|
163
|
+
supportsThinkingOff: supportedEffortLevels.length === 0 || value === "haiku"
|
|
164
|
+
});
|
|
165
|
+
/** The 14 rows the spike saw from `supportedModels()` on CLI 2.1.302. */
|
|
166
|
+
const FAKE_CLAUDE_MODELS = [
|
|
167
|
+
row("default", "claude-opus-5-5", "Default (Opus 5.5)"),
|
|
168
|
+
row("opus", "claude-opus-5-5", "Opus 5.5"),
|
|
169
|
+
row("fable", "claude-fable-5-1", "Fable 5.1"),
|
|
170
|
+
row("sonnet", "claude-sonnet-5-5", "Sonnet 5.5"),
|
|
171
|
+
row("haiku", "claude-haiku-5-5", "Haiku 5.5", NO_EFFORT),
|
|
172
|
+
row("claude-haiku-4-5-20251001", "claude-haiku-4-5-20251001", "Haiku 4.5", NO_EFFORT),
|
|
173
|
+
row("claude-sonnet-5", "claude-sonnet-5", "Sonnet 5"),
|
|
174
|
+
row("claude-opus-5", "claude-opus-5", "Opus 5"),
|
|
175
|
+
row("claude-fable-5", "claude-fable-5", "Fable 5"),
|
|
176
|
+
row("claude-opus-4-8", "claude-opus-4-8", "Opus 4.8"),
|
|
177
|
+
row("claude-opus-4-7", "claude-opus-4-7", "Opus 4.7"),
|
|
178
|
+
row("claude-opus-4-6", "claude-opus-4-6", "Opus 4.6"),
|
|
179
|
+
row("claude-sonnet-4-6", "claude-sonnet-4-6", "Sonnet 4.6"),
|
|
180
|
+
row("claude-mythos-5-1", "claude-mythos-5-1", "Mythos 5.1")
|
|
181
|
+
];
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region ../core/src/claude-cli/testing/message-channel.ts
|
|
184
|
+
function createMessageChannel() {
|
|
185
|
+
const queue = [];
|
|
186
|
+
let ended = false;
|
|
187
|
+
let wake;
|
|
188
|
+
async function* iterate() {
|
|
189
|
+
while (true) {
|
|
190
|
+
while (queue.length === 0 && !ended) await new Promise((resolve) => wake = resolve);
|
|
191
|
+
const next = queue.shift();
|
|
192
|
+
if (next === void 0) return;
|
|
193
|
+
yield next;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
[Symbol.asyncIterator]: iterate,
|
|
198
|
+
push(value) {
|
|
199
|
+
if (ended) return;
|
|
200
|
+
queue.push(value);
|
|
201
|
+
wake?.();
|
|
202
|
+
},
|
|
203
|
+
end() {
|
|
204
|
+
ended = true;
|
|
205
|
+
wake?.();
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region ../core/src/claude-cli/testing/fake-claude-query.ts
|
|
211
|
+
const INTERRUPTED_TEXT = "The tool call was interrupted by the user.";
|
|
212
|
+
const textOfInput = (message) => typeof message.message.content === "string" ? message.message.content : message.message.content.map((block) => block.type === "text" ? block.text : "[image]").join("\n");
|
|
213
|
+
const resolvedModel = (model) => FAKE_CLAUDE_MODELS.find((row) => row.value === model)?.resolvedModel ?? model;
|
|
214
|
+
const textOfResult = (result) => result.content.map((block) => block.type === "text" ? block.text : "[image]").join("\n");
|
|
215
|
+
const nextTick = () => new Promise((resolve) => setImmediate(resolve));
|
|
216
|
+
const sleepMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
217
|
+
const deniedText = (toolName) => `Permission to use ${toolName} has been denied.`;
|
|
218
|
+
const unavailableText = (toolName) => `Error: No such tool available: ${toolName}`;
|
|
219
|
+
const cliNameOf = (use) => use.connector ? connectorCliName(use.connector.server, use.name) : `${TOOL_NAME_PREFIX}${use.name}`;
|
|
220
|
+
function createFakeQuery(deps) {
|
|
221
|
+
const { options, record, script, ids } = deps;
|
|
222
|
+
const out = createMessageChannel();
|
|
223
|
+
const pending = [];
|
|
224
|
+
let model = options.model;
|
|
225
|
+
let turnIndex = 0;
|
|
226
|
+
let initSent = false;
|
|
227
|
+
let inputDone = false;
|
|
228
|
+
let wake;
|
|
229
|
+
let interruptTurn;
|
|
230
|
+
let turnInterrupted;
|
|
231
|
+
const mcp = createFakeMcpState({
|
|
232
|
+
open: connectorsAreOpen(options),
|
|
233
|
+
connectors: deps.connectors,
|
|
234
|
+
tauTools: () => (options.toolHost?.listTools() ?? []).map((tool) => `${TOOL_NAME_PREFIX}${tool.name}`),
|
|
235
|
+
pendingPolls: deps.pendingPolls,
|
|
236
|
+
toggles: record.toggles
|
|
237
|
+
});
|
|
238
|
+
const emit = (message) => out.push(message);
|
|
239
|
+
const sessionId = () => record.sessionId;
|
|
240
|
+
async function readInput() {
|
|
241
|
+
if (typeof options.prompt === "string") {
|
|
242
|
+
pending.push(options.prompt);
|
|
243
|
+
record.inputs.push(options.prompt);
|
|
244
|
+
inputDone = true;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
for await (const message of options.prompt) {
|
|
248
|
+
const text = textOfInput(message);
|
|
249
|
+
record.inputs.push(text);
|
|
250
|
+
pending.push(text);
|
|
251
|
+
wake?.();
|
|
252
|
+
}
|
|
253
|
+
inputDone = true;
|
|
254
|
+
}
|
|
255
|
+
const emitApiMessage = (blocks, stopReason, error) => {
|
|
256
|
+
const message = {
|
|
257
|
+
id: ids.messageId(),
|
|
258
|
+
model: resolvedModel(model),
|
|
259
|
+
blocks,
|
|
260
|
+
stopReason,
|
|
261
|
+
error
|
|
262
|
+
};
|
|
263
|
+
for (const event of apiMessageEvents(sessionId(), message, ids)) emit(event);
|
|
264
|
+
};
|
|
265
|
+
const emitResult = (outcome) => emit(resultEvent(sessionId(), resolvedModel(model), outcome, ids.uuid()));
|
|
266
|
+
function foldPending() {
|
|
267
|
+
if (pending.length === 0) return;
|
|
268
|
+
record.folded.push({
|
|
269
|
+
turnIndex,
|
|
270
|
+
texts: pending.splice(0)
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
async function runTool(use, toolUseId) {
|
|
274
|
+
const host = options.toolHost;
|
|
275
|
+
if (!host) throw new Error("The fake SDK got a tool call but no tool host.");
|
|
276
|
+
const args = use.arguments ?? {};
|
|
277
|
+
record.toolCalls.push({
|
|
278
|
+
name: use.name,
|
|
279
|
+
toolUseId,
|
|
280
|
+
arguments: args
|
|
281
|
+
});
|
|
282
|
+
const call = host.callTool({
|
|
283
|
+
name: use.name,
|
|
284
|
+
arguments: args,
|
|
285
|
+
toolUseId
|
|
286
|
+
});
|
|
287
|
+
return turnInterrupted ? Promise.race([call, turnInterrupted]) : call;
|
|
288
|
+
}
|
|
289
|
+
async function askPermission(use, toolName, toolUseId) {
|
|
290
|
+
if (isAllowedByList(options.allowedTools, toolName)) return {
|
|
291
|
+
behavior: "allow",
|
|
292
|
+
updatedInput: use.arguments ?? {}
|
|
293
|
+
};
|
|
294
|
+
if (!options.canUseTool || options.permissionMode === "dontAsk") return {
|
|
295
|
+
behavior: "deny",
|
|
296
|
+
message: deniedText(toolName)
|
|
297
|
+
};
|
|
298
|
+
const controller = new AbortController();
|
|
299
|
+
const asked = options.canUseTool({
|
|
300
|
+
toolName,
|
|
301
|
+
input: use.arguments ?? {},
|
|
302
|
+
toolUseId,
|
|
303
|
+
signal: controller.signal,
|
|
304
|
+
mcpServer: {
|
|
305
|
+
name: use.connector?.server ?? "",
|
|
306
|
+
source: "claudeai"
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
const decision = await Promise.race([asked, turnInterrupted ?? asked]);
|
|
310
|
+
const settled = decision === "interrupted" ? "aborted" : decision.behavior;
|
|
311
|
+
record.permissionRequests.push({
|
|
312
|
+
toolName,
|
|
313
|
+
toolUseId,
|
|
314
|
+
decision: settled
|
|
315
|
+
});
|
|
316
|
+
if (decision === "interrupted") controller.abort();
|
|
317
|
+
return decision;
|
|
318
|
+
}
|
|
319
|
+
async function runConnector(use, toolUseId) {
|
|
320
|
+
const run = use.connector;
|
|
321
|
+
if (!run) return "done";
|
|
322
|
+
const toolName = cliNameOf(use);
|
|
323
|
+
const call = {
|
|
324
|
+
toolName,
|
|
325
|
+
toolUseId,
|
|
326
|
+
ran: false
|
|
327
|
+
};
|
|
328
|
+
record.connectorCalls.push(call);
|
|
329
|
+
if (!mcp.activeToolNames().includes(toolName)) {
|
|
330
|
+
emitToolResult(toolUseId, unavailableText(toolName), true);
|
|
331
|
+
return "done";
|
|
332
|
+
}
|
|
333
|
+
const decision = await askPermission(use, toolName, toolUseId);
|
|
334
|
+
if (decision === "interrupted") return "interrupted";
|
|
335
|
+
if (decision.behavior === "deny") {
|
|
336
|
+
emitToolResult(toolUseId, decision.message, true);
|
|
337
|
+
return "done";
|
|
338
|
+
}
|
|
339
|
+
if (await Promise.race([sleepMs(run.delayMs ?? 0), turnInterrupted ?? sleepMs(0)]) === "interrupted") return "interrupted";
|
|
340
|
+
call.ran = true;
|
|
341
|
+
emitToolResult(toolUseId, run.result, run.isError ?? false);
|
|
342
|
+
return "done";
|
|
343
|
+
}
|
|
344
|
+
async function runTauTool(use, toolUseId) {
|
|
345
|
+
const result = await runTool(use, toolUseId);
|
|
346
|
+
if (result === "interrupted") return "interrupted";
|
|
347
|
+
emitToolResult(toolUseId, textOfResult(result), result.isError);
|
|
348
|
+
return "done";
|
|
349
|
+
}
|
|
350
|
+
function emitToolResult(toolUseId, text, isError) {
|
|
351
|
+
record.toolResults.push({
|
|
352
|
+
toolUseId,
|
|
353
|
+
text,
|
|
354
|
+
isError
|
|
355
|
+
});
|
|
356
|
+
emit(toolResultEvent(sessionId(), toolUseId, text, isError, ids.uuid()));
|
|
357
|
+
}
|
|
358
|
+
async function runToolsStep(step) {
|
|
359
|
+
const uses = step.calls.map((call) => ({
|
|
360
|
+
call,
|
|
361
|
+
toolUseId: ids.toolUseId()
|
|
362
|
+
}));
|
|
363
|
+
const blocks = uses.map(({ call, toolUseId }) => ({
|
|
364
|
+
type: "tool_use",
|
|
365
|
+
id: toolUseId,
|
|
366
|
+
name: cliNameOf(call),
|
|
367
|
+
input: call.arguments ?? {}
|
|
368
|
+
}));
|
|
369
|
+
emitApiMessage([...step.text ? [{
|
|
370
|
+
type: "text",
|
|
371
|
+
text: step.text
|
|
372
|
+
}] : [], ...blocks], "tool_use");
|
|
373
|
+
for (const [index, { call, toolUseId }] of uses.entries()) if ((call.connector ? await runConnector(call, toolUseId) : await runTauTool(call, toolUseId)) === "interrupted") {
|
|
374
|
+
for (const open of uses.slice(index)) emitToolResult(open.toolUseId, INTERRUPTED_TEXT, true);
|
|
375
|
+
return "interrupted";
|
|
376
|
+
}
|
|
377
|
+
return "continue";
|
|
378
|
+
}
|
|
379
|
+
async function runStep(step) {
|
|
380
|
+
if (step.kind === "rateLimit") {
|
|
381
|
+
emit(rateLimitEvent(sessionId(), step.limit, ids.uuid()));
|
|
382
|
+
return "continue";
|
|
383
|
+
}
|
|
384
|
+
if (step.kind === "text") {
|
|
385
|
+
const thinking = step.thinking ? [{
|
|
386
|
+
type: "thinking",
|
|
387
|
+
thinking: step.thinking
|
|
388
|
+
}] : [];
|
|
389
|
+
emitApiMessage([...thinking, {
|
|
390
|
+
type: "text",
|
|
391
|
+
text: step.text
|
|
392
|
+
}], "end_turn");
|
|
393
|
+
emitResult("success");
|
|
394
|
+
return "ended";
|
|
395
|
+
}
|
|
396
|
+
if (step.kind === "error") {
|
|
397
|
+
emitApiMessage([{
|
|
398
|
+
type: "text",
|
|
399
|
+
text: step.text ?? step.code
|
|
400
|
+
}], "end_turn", step.code);
|
|
401
|
+
emitResult("error");
|
|
402
|
+
return "ended";
|
|
403
|
+
}
|
|
404
|
+
return runToolsStep(step);
|
|
405
|
+
}
|
|
406
|
+
async function runTurn(firstUserText) {
|
|
407
|
+
turnInterrupted = new Promise((resolve) => {
|
|
408
|
+
interruptTurn = () => resolve("interrupted");
|
|
409
|
+
});
|
|
410
|
+
if (!initSent) {
|
|
411
|
+
initSent = true;
|
|
412
|
+
const names = [...(options.toolHost?.listTools() ?? []).map((tool) => `${TOOL_NAME_PREFIX}${tool.name}`), ...mcp.activeToolNames()];
|
|
413
|
+
emit(initEvent(sessionId(), resolvedModel(model), names, deps.apiKeySource));
|
|
414
|
+
}
|
|
415
|
+
const steps = script(turnIndex, firstUserText, options.toolHost !== void 0);
|
|
416
|
+
for (const step of steps) {
|
|
417
|
+
await nextTick();
|
|
418
|
+
const outcome = await runStep(step);
|
|
419
|
+
if (outcome === "interrupted") return void emitResult("interrupted");
|
|
420
|
+
if (outcome === "ended") return;
|
|
421
|
+
foldPending();
|
|
422
|
+
}
|
|
423
|
+
emitResult("success");
|
|
424
|
+
}
|
|
425
|
+
async function drive() {
|
|
426
|
+
while (!record.closed) {
|
|
427
|
+
while (pending.length === 0 && !record.closed && !inputDone) await new Promise((resolve) => wake = resolve);
|
|
428
|
+
const first = pending.shift();
|
|
429
|
+
if (first === void 0) return;
|
|
430
|
+
await runTurn(first);
|
|
431
|
+
turnIndex += 1;
|
|
432
|
+
interruptTurn = void 0;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
readInput().finally(() => wake?.());
|
|
436
|
+
drive().finally(() => out.end());
|
|
437
|
+
return {
|
|
438
|
+
[Symbol.asyncIterator]: () => out[Symbol.asyncIterator](),
|
|
439
|
+
async interrupt() {
|
|
440
|
+
record.interrupts += 1;
|
|
441
|
+
interruptTurn?.();
|
|
442
|
+
},
|
|
443
|
+
...mcp.control,
|
|
444
|
+
async setModel(next) {
|
|
445
|
+
record.models.push(next);
|
|
446
|
+
model = next;
|
|
447
|
+
},
|
|
448
|
+
async applyFlagSettings(settings) {
|
|
449
|
+
record.flags.push({ ...settings });
|
|
450
|
+
},
|
|
451
|
+
async isAutoCompactEnabled() {
|
|
452
|
+
return options.settings.autoCompactEnabled !== false;
|
|
453
|
+
},
|
|
454
|
+
close() {
|
|
455
|
+
record.closed = true;
|
|
456
|
+
wake?.();
|
|
457
|
+
interruptTurn?.();
|
|
458
|
+
out.end();
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
463
|
+
//#region ../core/src/claude-cli/testing/fake-claude-script.ts
|
|
464
|
+
const sayOk = () => [{
|
|
465
|
+
kind: "text",
|
|
466
|
+
text: "ok"
|
|
467
|
+
}];
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region ../core/src/claude-cli/testing/fake-claude-sdk.ts
|
|
470
|
+
const FAKE_LOGGED_IN = {
|
|
471
|
+
loggedIn: true,
|
|
472
|
+
authMethod: "claude.ai",
|
|
473
|
+
subscriptionType: "team",
|
|
474
|
+
orgName: "Fake Org"
|
|
475
|
+
};
|
|
476
|
+
function createFakeClaudeSdk(options = {}) {
|
|
477
|
+
const queries = [];
|
|
478
|
+
const deletedSessions = [];
|
|
479
|
+
const ids = createMessageIds();
|
|
480
|
+
let script = options.script ?? sayOk;
|
|
481
|
+
let auth = options.auth ?? FAKE_LOGGED_IN;
|
|
482
|
+
let sessions = 0;
|
|
483
|
+
let loads = 0;
|
|
484
|
+
const probes = [];
|
|
485
|
+
const connectors = options.connectors ?? [];
|
|
486
|
+
const pendingPolls = options.pendingPolls ?? 0;
|
|
487
|
+
const startProbe = (probeOptions = {}) => {
|
|
488
|
+
probes.push({ settingSources: probeOptions.settingSources ?? [] });
|
|
489
|
+
const mcp = createFakeMcpState({
|
|
490
|
+
open: true,
|
|
491
|
+
connectors,
|
|
492
|
+
tauTools: () => [],
|
|
493
|
+
pendingPolls,
|
|
494
|
+
toggles: []
|
|
495
|
+
});
|
|
496
|
+
return {
|
|
497
|
+
...mcp.control,
|
|
498
|
+
contextSummary: async () => mcp.context(),
|
|
499
|
+
close: () => void 0
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
const sessionFor = (query) => query.resume && !query.forkSession ? query.resume : `fake-session-${++sessions}`;
|
|
503
|
+
const port = {
|
|
504
|
+
async authStatus() {
|
|
505
|
+
if (auth instanceof Error) throw auth;
|
|
506
|
+
return auth;
|
|
507
|
+
},
|
|
508
|
+
async accountInfo() {
|
|
509
|
+
return options.account ?? {
|
|
510
|
+
organization: "Fake Org",
|
|
511
|
+
subscriptionType: "Claude Team"
|
|
512
|
+
};
|
|
513
|
+
},
|
|
514
|
+
async supportedModels() {
|
|
515
|
+
return options.models ?? FAKE_CLAUDE_MODELS;
|
|
516
|
+
},
|
|
517
|
+
async bundledCliVersion() {
|
|
518
|
+
return options.bundledCliVersion ?? "0.0.0-fake";
|
|
519
|
+
},
|
|
520
|
+
startProbe,
|
|
521
|
+
startQuery(queryOptions) {
|
|
522
|
+
const record = {
|
|
523
|
+
options: queryOptions,
|
|
524
|
+
sessionId: sessionFor(queryOptions),
|
|
525
|
+
inputs: [],
|
|
526
|
+
folded: [],
|
|
527
|
+
toolCalls: [],
|
|
528
|
+
toolResults: [],
|
|
529
|
+
models: [],
|
|
530
|
+
flags: [],
|
|
531
|
+
interrupts: 0,
|
|
532
|
+
closed: false,
|
|
533
|
+
permissionRequests: [],
|
|
534
|
+
connectorCalls: [],
|
|
535
|
+
toggles: []
|
|
536
|
+
};
|
|
537
|
+
queries.push(record);
|
|
538
|
+
return createFakeQuery({
|
|
539
|
+
options: queryOptions,
|
|
540
|
+
record,
|
|
541
|
+
script: (turn, text, hasTools) => script(turn, text, hasTools),
|
|
542
|
+
ids,
|
|
543
|
+
apiKeySource: options.apiKeySource ?? "none",
|
|
544
|
+
connectors,
|
|
545
|
+
pendingPolls
|
|
546
|
+
});
|
|
547
|
+
},
|
|
548
|
+
async deleteSession(sessionId) {
|
|
549
|
+
deletedSessions.push(sessionId);
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
return {
|
|
553
|
+
port,
|
|
554
|
+
queries,
|
|
555
|
+
deletedSessions,
|
|
556
|
+
probes,
|
|
557
|
+
loadCount: () => loads,
|
|
558
|
+
async load() {
|
|
559
|
+
loads += 1;
|
|
560
|
+
return port;
|
|
561
|
+
},
|
|
562
|
+
setScript(next) {
|
|
563
|
+
script = next;
|
|
564
|
+
},
|
|
565
|
+
setAuth(next) {
|
|
566
|
+
auth = next;
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
//#endregion
|
|
571
|
+
export { FAKE_LOGGED_IN, createFakeClaudeSdk };
|