@velum-labs/routekit-tool-claude 0.9.0

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.
@@ -0,0 +1,153 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import { driverContractSuite } from "@velum-labs/routekit-harness-core/testing";
4
+ import { createClaudeDriver } from "../driver.js";
5
+ const usage = { input_tokens: 5, output_tokens: 3 };
6
+ /**
7
+ * A scripted stand-in for the Agent SDK `query`: emits a system/init, an
8
+ * assistant text block echoing the prompt, invokes canUseTool when the prompt
9
+ * asks to APPROVE, and finishes with a result. Honors the abort controller so
10
+ * the aborted-turn contract holds.
11
+ */
12
+ function scriptedQuery(sessionId = "claude-session-1") {
13
+ return ({ prompt, options }) => {
14
+ const controller = options.abortController;
15
+ async function* run() {
16
+ yield {
17
+ type: "system",
18
+ subtype: "init",
19
+ session_id: sessionId
20
+ };
21
+ if (controller?.signal.aborted === true) {
22
+ throw Object.assign(new Error("aborted"), { name: "AbortError" });
23
+ }
24
+ if (prompt.includes("APPROVE") && options.canUseTool !== undefined) {
25
+ const result = await options.canUseTool("Bash", { command: "npm test" }, {
26
+ signal: controller?.signal ?? new AbortController().signal,
27
+ toolUseID: "tool-use-1"
28
+ });
29
+ if (result.behavior === "deny") {
30
+ yield {
31
+ type: "result",
32
+ subtype: "error_during_execution",
33
+ usage,
34
+ session_id: sessionId
35
+ };
36
+ return;
37
+ }
38
+ }
39
+ yield {
40
+ type: "assistant",
41
+ message: { content: [{ type: "text", text: `echo: ${prompt}` }] },
42
+ session_id: sessionId
43
+ };
44
+ yield {
45
+ type: "result",
46
+ subtype: "success",
47
+ result: `echo: ${prompt}`,
48
+ usage,
49
+ session_id: sessionId
50
+ };
51
+ }
52
+ const generator = run();
53
+ const query = generator;
54
+ query.interrupt = async () => {
55
+ controller?.abort();
56
+ };
57
+ return query;
58
+ };
59
+ }
60
+ const driver = createClaudeDriver({ queryFn: scriptedQuery() });
61
+ driverContractSuite({
62
+ name: "claude driver",
63
+ createInstance: async () => driver.createInstance(driver.configSchema.parse({ command: "claude" })),
64
+ startOptions: () => ({ cwd: process.cwd() }),
65
+ supportsResume: true,
66
+ turnTimeoutMs: 10_000
67
+ });
68
+ test("claude driver maps SDK messages into canonical events", async () => {
69
+ const instance = await driver.createInstance(driver.configSchema.parse({ command: "claude" }));
70
+ try {
71
+ const session = await instance.startSession({ cwd: process.cwd() });
72
+ const events = [];
73
+ for await (const event of session.sendTurn({ prompt: "hello claude" })) {
74
+ events.push(event);
75
+ }
76
+ assert.ok(events.some((event) => event.type === "session.started"));
77
+ const delta = events.find((event) => event.type === "content.delta");
78
+ assert.ok(delta && delta.text.includes("hello claude"));
79
+ const completed = events.find((event) => event.type === "turn.completed");
80
+ assert.equal(completed?.endReason, "completed");
81
+ assert.equal(session.resumeCursor()?.data && (session.resumeCursor()?.data).sessionId, "claude-session-1");
82
+ assert.ok(events.every((event) => event.kind === "claude_code"));
83
+ await session.stop();
84
+ }
85
+ finally {
86
+ await instance.dispose();
87
+ }
88
+ });
89
+ test("claude driver forwards opaque effort through SDK options", async () => {
90
+ let observed;
91
+ const delegate = scriptedQuery("claude-reasoning-session");
92
+ const effortDriver = createClaudeDriver({
93
+ queryFn: (input) => {
94
+ observed = input.options;
95
+ return delegate(input);
96
+ }
97
+ });
98
+ const instance = await effortDriver.createInstance(effortDriver.configSchema.parse({ command: "claude" }));
99
+ try {
100
+ const session = await instance.startSession({
101
+ cwd: process.cwd(),
102
+ reasoning: { mode: "effort", effort: "deep" }
103
+ });
104
+ for await (const _event of session.sendTurn({ prompt: "reason deeply" })) {
105
+ // Drain.
106
+ }
107
+ assert.deepEqual(observed?.thinking, { type: "adaptive" });
108
+ assert.equal(observed?.effort, "deep");
109
+ }
110
+ finally {
111
+ await instance.dispose();
112
+ }
113
+ });
114
+ test("claude driver auto-approves tools under the automation policy", async () => {
115
+ const instance = await driver.createInstance(driver.configSchema.parse({ command: "claude" }));
116
+ try {
117
+ const session = await instance.startSession({ cwd: process.cwd() });
118
+ const events = [];
119
+ for await (const event of session.sendTurn({ prompt: "please APPROVE the tool" })) {
120
+ events.push(event);
121
+ }
122
+ assert.ok(!events.some((event) => event.type === "request.opened"));
123
+ assert.equal(events.find((event) => event.type === "turn.completed")?.endReason, "completed");
124
+ await session.stop();
125
+ }
126
+ finally {
127
+ await instance.dispose();
128
+ }
129
+ });
130
+ test("claude driver surfaces approvals under autoApprove none and resolves them", async () => {
131
+ const instance = await driver.createInstance(driver.configSchema.parse({ command: "claude" }));
132
+ try {
133
+ const session = await instance.startSession({
134
+ cwd: process.cwd(),
135
+ approvalPolicy: { autoApprove: "none" }
136
+ });
137
+ const events = [];
138
+ for await (const event of session.sendTurn({ prompt: "please APPROVE the tool" })) {
139
+ events.push(event);
140
+ if (event.type === "request.opened") {
141
+ assert.equal(event.requestType, "exec_command_approval");
142
+ await session.respondToRequest(event.requestId, "accept");
143
+ }
144
+ }
145
+ assert.ok(events.some((event) => event.type === "request.opened"));
146
+ assert.ok(events.some((event) => event.type === "request.resolved"));
147
+ assert.equal(events.find((event) => event.type === "turn.completed")?.endReason, "completed");
148
+ await session.stop();
149
+ }
150
+ finally {
151
+ await instance.dispose();
152
+ }
153
+ });
@@ -0,0 +1 @@
1
+ export {};