@kiwa-test/agent 0.2.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.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # @kiwa-test/agent
2
+
3
+ Agent orchestration mock harness for kiwa — LangGraph 型 state machine + OpenAI Assistants v2 の 1 統一 API。
4
+
5
+ LangGraph は agent graph の DSL、 OpenAI Assistants v2 は stateful conversation + run status polling model。 本 package は kiwa test で real LangGraph runtime / real OpenAI API を叩かずに agent orchestration chain を組み立てるための in-process implementation を提供する。
6
+
7
+ - `StateGraph` — LangGraph 型 API (`addNode` + `addEdge` + `compile` → `invoke` / `stream`)
8
+ - `StateMachine` — 低水準 state graph 実行 engine、 `StateGraph` の backing store
9
+ - `AssistantsClient` — Assistants v2 mock (`createAssistant` + `createThread` + `addMessage` + `createRun` + `poll` + `submitToolOutputs`)
10
+ - deterministic id 発行 (`idSeed` 指定で snapshot test 可)、 6 compile validation + max steps guard で runaway loop 遮断
11
+
12
+ ## Usage — LangGraph 型 state machine
13
+
14
+ ```ts
15
+ import { END, START, StateGraph } from '@kiwa-test/agent';
16
+
17
+ interface ChatState {
18
+ messages: string[];
19
+ intent: string | null;
20
+ reply: string | null;
21
+ }
22
+
23
+ const graph = new StateGraph<ChatState>()
24
+ .addNode('classify', (s) => ({
25
+ intent: s.messages[0]?.startsWith('/help') ? 'help' : 'chat',
26
+ }))
27
+ .addNode('answer', (s) => ({
28
+ reply: s.intent === 'help' ? 'help: A, B, C' : `chat: ${s.messages[0]}`,
29
+ }))
30
+ .addEdge(START, 'classify')
31
+ .addEdge('classify', 'answer')
32
+ .addEdge('answer', END);
33
+
34
+ const compiled = graph.compile();
35
+ const final = await compiled.invoke({ messages: ['/help me'], intent: null, reply: null });
36
+ // final.intent === 'help'
37
+ // final.reply === 'help: A, B, C'
38
+
39
+ // stream で中間 state を順次観測
40
+ for await (const step of compiled.stream({ messages: ['hi'], intent: null, reply: null })) {
41
+ console.log(step.node, step.patch);
42
+ }
43
+ ```
44
+
45
+ ## Usage — OpenAI Assistants v2
46
+
47
+ ```ts
48
+ import { AssistantsClient, toolCall } from '@kiwa-test/agent';
49
+
50
+ const client = new AssistantsClient({ idSeed: 'demo' });
51
+
52
+ const assistant = client.createAssistant({
53
+ name: 'weather-agent',
54
+ instructions: 'answer weather with the weather tool',
55
+ handler: async (ctx) => {
56
+ if (ctx.toolOutputs === undefined) {
57
+ return {
58
+ kind: 'tool_calls',
59
+ toolCalls: [toolCall({ id: 'c1', name: 'weather', arguments: { city: 'tokyo' } })],
60
+ };
61
+ }
62
+ return { kind: 'message', content: `weather: ${ctx.toolOutputs[0]?.output}` };
63
+ },
64
+ });
65
+
66
+ const thread = client.createThread({ messages: [{ role: 'user', content: 'weather in tokyo?' }] });
67
+ const run = client.createRun({ threadId: thread.id, assistantId: assistant.id });
68
+
69
+ const step1 = await client.poll(run.id);
70
+ // step1.status === 'requires_action'
71
+ // step1.requiredAction.toolCalls === [ { id: 'c1', function: { name: 'weather', arguments: '{"city":"tokyo"}' }, ... } ]
72
+
73
+ client.submitToolOutputs(run.id, {
74
+ toolOutputs: [{ toolCallId: 'c1', output: 'sunny 22C' }],
75
+ });
76
+
77
+ const step2 = await client.poll(run.id);
78
+ // step2.status === 'completed'
79
+ // thread.messages.at(-1) === { role: 'assistant', content: 'weather: sunny 22C' }
80
+ ```
81
+
82
+ ## LangGraph → real 対応表
83
+
84
+ | real LangGraph API | kiwa mock 対応 |
85
+ |---|---|
86
+ | `new StateGraph(channels)` | `new StateGraph<TState>()` (v0.1 は shallow merge) |
87
+ | `graph.addNode(name, fn)` | `graph.addNode(name, handler)` |
88
+ | `graph.addEdge(from, to)` | `graph.addEdge(from, to)` (unconditional) |
89
+ | `graph.setEntryPoint(name)` | `graph.addEdge(START, name)` に統一 |
90
+ | `graph.setFinishPoint(name)` | `graph.addEdge(name, END)` に統一 |
91
+ | `graph.compile()` | `graph.compile()` → `CompiledGraph` |
92
+ | `compiled.invoke(state)` | `compiled.invoke(state)` |
93
+ | `compiled.stream(state)` | `compiled.stream(state)` async generator |
94
+
95
+ ## Assistants v2 → real 対応表
96
+
97
+ | real Assistants v2 API | kiwa mock 対応 |
98
+ |---|---|
99
+ | `openai.beta.assistants.create` | `client.createAssistant({ name, instructions, handler })` |
100
+ | `openai.beta.threads.create` | `client.createThread({ messages? })` |
101
+ | `openai.beta.threads.messages.create` | `client.addMessage(threadId, { role, content })` |
102
+ | `openai.beta.threads.runs.create` | `client.createRun({ threadId, assistantId })` |
103
+ | `openai.beta.threads.runs.retrieve` | `client.poll(runId)` |
104
+ | `openai.beta.threads.runs.submitToolOutputs` | `client.submitToolOutputs(runId, { toolOutputs })` |
105
+ | `openai.beta.threads.runs.cancel` | `client.cancel(runId)` |
106
+
107
+ ## Run status transition
108
+
109
+ 1. `createRun()` → status = **queued**
110
+ 2. 1 回目の `poll()` → handler を呼び、 結果に応じて
111
+ - `{ kind: 'message' }` → **completed** + assistant message を thread に append
112
+ - `{ kind: 'tool_calls' }` → **requires_action** + pending tool_calls を保持
113
+ - handler が throw → **failed** + lastError = { code: 'handler_error', message }
114
+ 3. **requires_action** 中に `submitToolOutputs()` → **queued** に戻し、 次 `poll()` で handler 再呼出 (context.toolOutputs で結果参照可能)
115
+ 4. `cancel()` → queued / requires_action / in_progress を **failed** に倒す (lastError.code = 'cancelled')
116
+
117
+ ## Compile validation (6 項目、 fail-fast)
118
+
119
+ 1. START edge が最低 1 本存在する
120
+ 2. START edge の to が存在する node (or END)
121
+ 3. 全 edge の to が存在する node (or END)
122
+ 4. 全 edge の from が存在する node (or START)
123
+ 5. 全 node に out-edge が最低 1 本存在する
124
+ 6. START edge は 1 本のみ
125
+
126
+ ## 未対応 (v0.2 以降)
127
+
128
+ - LangGraph — `addConditionalEdges` / `channels` reducer / `interrupt` / `checkpointer`
129
+ - Assistants v2 — vector store / `file_search` / `code_interpreter` / streaming (SSE)
130
+
131
+ ## License
132
+
133
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,536 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AssistantsClient: () => AssistantsClient,
24
+ CompiledGraph: () => CompiledGraph,
25
+ DEFAULT_MAX_STEPS: () => DEFAULT_MAX_STEPS,
26
+ END: () => END,
27
+ GraphCompileError: () => GraphCompileError,
28
+ MaxStepsExceededError: () => MaxStepsExceededError,
29
+ START: () => START,
30
+ StateGraph: () => StateGraph,
31
+ StateMachine: () => StateMachine,
32
+ toolCall: () => toolCall
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/types.ts
37
+ var END = "__end__";
38
+ var START = "__start__";
39
+
40
+ // src/state-machine.ts
41
+ var DEFAULT_MAX_STEPS = 100;
42
+ var GraphCompileError = class extends Error {
43
+ constructor(message) {
44
+ super(message);
45
+ this.name = "GraphCompileError";
46
+ }
47
+ };
48
+ var MaxStepsExceededError = class extends Error {
49
+ steps;
50
+ constructor(steps) {
51
+ super(`state machine exceeded max steps: ${steps}`);
52
+ this.name = "MaxStepsExceededError";
53
+ this.steps = steps;
54
+ }
55
+ };
56
+ var StateMachine = class {
57
+ nodes = /* @__PURE__ */ new Map();
58
+ edges = [];
59
+ compiled = false;
60
+ /** node を登録。 同名 node は上書きする。 */
61
+ addNode(name, handler) {
62
+ if (!name || name.trim() === "") {
63
+ throw new GraphCompileError("node name must be a non-empty string");
64
+ }
65
+ if (name === START || name === END) {
66
+ throw new GraphCompileError(`node name "${name}" is reserved`);
67
+ }
68
+ this.nodes.set(name, handler);
69
+ this.compiled = false;
70
+ return this;
71
+ }
72
+ /** edge を追加。 from / to は node 名 or START / END sentinel。 */
73
+ addEdge(from, to) {
74
+ if (!from || !to) {
75
+ throw new GraphCompileError("edge from/to must be non-empty strings");
76
+ }
77
+ this.edges.push({ from, to });
78
+ this.compiled = false;
79
+ return this;
80
+ }
81
+ /** node 数 (test / debug 用)。 */
82
+ get nodeCount() {
83
+ return this.nodes.size;
84
+ }
85
+ /** edge 数 (test / debug 用)。 */
86
+ get edgeCount() {
87
+ return this.edges.length;
88
+ }
89
+ /** compile 済かどうか (test / debug 用)。 */
90
+ get isCompiled() {
91
+ return this.compiled;
92
+ }
93
+ /**
94
+ * validate + compile — validate 6 項目を fail-fast で確認、 pass なら
95
+ * `compiled = true` を立てて invoke / stream 可能状態にする。
96
+ */
97
+ compile() {
98
+ const startEdges = this.edges.filter((e) => e.from === START);
99
+ if (startEdges.length === 0) {
100
+ throw new GraphCompileError(
101
+ 'graph has no START edge \u2014 addEdge(START, "first_node") is required'
102
+ );
103
+ }
104
+ if (startEdges.length > 1) {
105
+ throw new GraphCompileError(
106
+ `graph has ${startEdges.length} START edges \u2014 v0.1 supports only 1 entry`
107
+ );
108
+ }
109
+ const startTo = startEdges[0].to;
110
+ if (startTo !== END && !this.nodes.has(startTo)) {
111
+ throw new GraphCompileError(`START edge targets unknown node: ${startTo}`);
112
+ }
113
+ const nodesWithOutEdge = /* @__PURE__ */ new Set();
114
+ for (const edge of this.edges) {
115
+ if (edge.from !== START && !this.nodes.has(edge.from)) {
116
+ throw new GraphCompileError(`edge.from references unknown node: ${edge.from}`);
117
+ }
118
+ if (edge.to !== END && !this.nodes.has(edge.to)) {
119
+ throw new GraphCompileError(`edge.to references unknown node: ${edge.to}`);
120
+ }
121
+ if (edge.from !== START) {
122
+ nodesWithOutEdge.add(edge.from);
123
+ }
124
+ }
125
+ for (const nodeName of this.nodes.keys()) {
126
+ if (!nodesWithOutEdge.has(nodeName)) {
127
+ throw new GraphCompileError(
128
+ `node "${nodeName}" has no outgoing edge \u2014 every node must connect to at least END`
129
+ );
130
+ }
131
+ }
132
+ this.compiled = true;
133
+ return this;
134
+ }
135
+ /**
136
+ * invoke — 初期 state から実行、 END に到達した final state を返す。 compile
137
+ * 未実施なら throw。
138
+ */
139
+ async invoke(initialState, options = {}) {
140
+ let last = initialState;
141
+ for await (const step of this.stream(initialState, options)) {
142
+ last = step.state;
143
+ }
144
+ return last;
145
+ }
146
+ /**
147
+ * stream — 各 node 実行後の {node, patch, state} を順次 yield。 END に到達した
148
+ * 時点で generator は終了する。
149
+ */
150
+ async *stream(initialState, options = {}) {
151
+ if (!this.compiled) {
152
+ throw new GraphCompileError("state machine not compiled \u2014 call compile() before invoke/stream");
153
+ }
154
+ const maxSteps = options.maxSteps ?? DEFAULT_MAX_STEPS;
155
+ let currentState = { ...initialState };
156
+ let currentNode = this.startNode();
157
+ let stepCount = 0;
158
+ while (currentNode !== END) {
159
+ stepCount += 1;
160
+ if (stepCount > maxSteps) {
161
+ throw new MaxStepsExceededError(stepCount);
162
+ }
163
+ const handler = this.nodes.get(currentNode);
164
+ if (!handler) {
165
+ throw new GraphCompileError(`runtime: node not found: ${currentNode}`);
166
+ }
167
+ const patch = await handler(currentState);
168
+ currentState = { ...currentState, ...patch };
169
+ yield { node: currentNode, patch, state: currentState };
170
+ const next = this.nextNode(currentNode);
171
+ if (next === void 0) {
172
+ throw new GraphCompileError(`runtime: no outgoing edge from node: ${currentNode}`);
173
+ }
174
+ currentNode = next;
175
+ }
176
+ }
177
+ startNode() {
178
+ const startEdge = this.edges.find((e) => e.from === START);
179
+ return startEdge.to;
180
+ }
181
+ nextNode(from) {
182
+ const edge = this.edges.find((e) => e.from === from);
183
+ return edge?.to;
184
+ }
185
+ };
186
+
187
+ // src/langgraph.ts
188
+ var StateGraph = class {
189
+ machine = new StateMachine();
190
+ /** node を追加。 handler は現 state から partial state を返す (同期 / 非同期両対応)。 */
191
+ addNode(name, handler) {
192
+ this.machine.addNode(name, handler);
193
+ return this;
194
+ }
195
+ /**
196
+ * edge を追加。 `from` は node 名 or `START`、 `to` は node 名 or `END`。
197
+ * v0.1 は unconditional edge のみ (conditional_edges は v0.2)。
198
+ */
199
+ addEdge(from, to) {
200
+ this.machine.addEdge(from, to);
201
+ return this;
202
+ }
203
+ /** compile + validate、 CompiledGraph を返す。 */
204
+ compile() {
205
+ this.machine.compile();
206
+ return new CompiledGraph(this.machine);
207
+ }
208
+ /** node 数 (test / debug 用)。 */
209
+ get nodeCount() {
210
+ return this.machine.nodeCount;
211
+ }
212
+ /** edge 数 (test / debug 用)。 */
213
+ get edgeCount() {
214
+ return this.machine.edgeCount;
215
+ }
216
+ };
217
+ var CompiledGraph = class {
218
+ constructor(machine) {
219
+ this.machine = machine;
220
+ }
221
+ machine;
222
+ /**
223
+ * invoke — 初期 state から実行し END 到達時の final state を返す。 中間 step
224
+ * を捨てて final だけ欲しい場合の shortcut。
225
+ */
226
+ async invoke(initialState, options) {
227
+ return this.machine.invoke(initialState, options);
228
+ }
229
+ /**
230
+ * stream — 各 node 実行後の GraphStep (node 名 + patch + merge 後 state) を
231
+ * 順次 yield。 END 到達時点で generator 終了。 real LangGraph の `stream()`
232
+ * (default mode = "values") に整合。
233
+ */
234
+ async *stream(initialState, options) {
235
+ for await (const step of this.machine.stream(initialState, options)) {
236
+ yield step;
237
+ }
238
+ }
239
+ };
240
+
241
+ // src/openai-assistants.ts
242
+ var AssistantsClient = class {
243
+ assistants = /* @__PURE__ */ new Map();
244
+ threads = /* @__PURE__ */ new Map();
245
+ runs = /* @__PURE__ */ new Map();
246
+ handlers = /* @__PURE__ */ new Map();
247
+ nextId = 1;
248
+ idSeed;
249
+ constructor(config = {}) {
250
+ this.idSeed = config.idSeed ?? "kiwa";
251
+ }
252
+ // ---- Assistant CRUD ------------------------------------------------
253
+ /**
254
+ * Assistant resource を発行。 real API と同じく id + name + instructions を持つ。
255
+ * handler は必須ではないが、 createRun() までに registerHandler() で紐付け必要。
256
+ */
257
+ createAssistant(params) {
258
+ const id = this.mintId("asst");
259
+ const assistant = {
260
+ id,
261
+ name: params.name,
262
+ instructions: params.instructions,
263
+ createdAt: Date.now()
264
+ };
265
+ this.assistants.set(id, assistant);
266
+ if (params.handler) {
267
+ this.handlers.set(id, params.handler);
268
+ }
269
+ return assistant;
270
+ }
271
+ /**
272
+ * assistant に handler を後付け登録。 test で「先に assistant を作って後で handler
273
+ * を差し替える」 シナリオ (behavior injection) 用。
274
+ */
275
+ registerHandler(assistantId, handler) {
276
+ if (!this.assistants.has(assistantId)) {
277
+ throw new Error(`unknown assistant id: ${assistantId}`);
278
+ }
279
+ this.handlers.set(assistantId, handler);
280
+ }
281
+ /** assistant 参照 (test / debug 用)。 */
282
+ getAssistant(id) {
283
+ return this.assistants.get(id);
284
+ }
285
+ // ---- Thread CRUD ---------------------------------------------------
286
+ /**
287
+ * Thread resource を発行。 初期 messages を渡すと user message として append される
288
+ * (real API と同じ挙動)。
289
+ */
290
+ createThread(params = {}) {
291
+ const id = this.mintId("thread");
292
+ const thread = {
293
+ id,
294
+ createdAt: Date.now(),
295
+ messages: []
296
+ };
297
+ this.threads.set(id, thread);
298
+ if (params.messages) {
299
+ for (const m of params.messages) {
300
+ this.addMessage(id, { role: m.role, content: m.content });
301
+ }
302
+ }
303
+ return thread;
304
+ }
305
+ /**
306
+ * Thread に message を append。 real API と同じく role は user / assistant、
307
+ * v0.1 は tool role 未対応 (Assistants v2 の tool message は submitToolOutputs
308
+ * 経路に統一)。
309
+ */
310
+ addMessage(threadId, params) {
311
+ const thread = this.threads.get(threadId);
312
+ if (!thread) {
313
+ throw new Error(`unknown thread id: ${threadId}`);
314
+ }
315
+ const message = {
316
+ id: this.mintId("msg"),
317
+ role: params.role,
318
+ content: params.content,
319
+ createdAt: Date.now()
320
+ };
321
+ thread.messages.push(message);
322
+ return message;
323
+ }
324
+ /** thread 参照 (test / debug 用、 messages は readonly view として返す)。 */
325
+ getThread(id) {
326
+ return this.threads.get(id);
327
+ }
328
+ // ---- Run lifecycle -------------------------------------------------
329
+ /**
330
+ * Run 発行 — thread + assistant を紐付けた Run resource (queued) を返す。 実際の
331
+ * assistant 実行は `poll(runId)` を呼び出した時に走る (real API の polling model と
332
+ * 同構造、 real でも create 直後は queued で 1 tick 後に進行する)。
333
+ */
334
+ createRun(params) {
335
+ if (!this.threads.has(params.threadId)) {
336
+ throw new Error(`unknown thread id: ${params.threadId}`);
337
+ }
338
+ if (!this.assistants.has(params.assistantId)) {
339
+ throw new Error(`unknown assistant id: ${params.assistantId}`);
340
+ }
341
+ if (!this.handlers.has(params.assistantId)) {
342
+ throw new Error(
343
+ `assistant ${params.assistantId} has no handler registered \u2014 call registerHandler() first`
344
+ );
345
+ }
346
+ const id = this.mintId("run");
347
+ const run = {
348
+ id,
349
+ threadId: params.threadId,
350
+ assistantId: params.assistantId,
351
+ status: "queued",
352
+ createdAt: Date.now()
353
+ };
354
+ this.runs.set(id, run);
355
+ return run;
356
+ }
357
+ /**
358
+ * poll — Run の 1 tick を進める。 real API polling は同じ retrieveRun で status
359
+ * を確認する model、 mock は「poll 呼出 = 1 tick 進行」 と扱う。 呼出後の Run
360
+ * (copy) を返す。 呼出前 status に応じて next status が deterministic に決まる。
361
+ *
362
+ * 1. queued → poll 1 回目で handler 呼出、 result に応じて completed / requires_action / failed
363
+ * 2. in_progress → poll 呼出でも遷移しない (v0.1 は 1 tick = 1 handler 呼出 model、
364
+ * in_progress は queued → completed の間の transient state として使用しない)、
365
+ * そのまま返す。 実質 queued と completed / requires_action / failed の 3 状態が
366
+ * caller に見える。
367
+ * 3. requires_action → poll でも遷移しない (submitToolOutputs 待ち)
368
+ * 4. completed / failed → 変化なし、 そのまま返す
369
+ */
370
+ async poll(runId) {
371
+ const run = this.runs.get(runId);
372
+ if (!run) {
373
+ throw new Error(`unknown run id: ${runId}`);
374
+ }
375
+ if (run.status !== "queued") {
376
+ return { ...run };
377
+ }
378
+ return this.executeRun(run);
379
+ }
380
+ /**
381
+ * pollUntilFinal — completed / failed / requires_action に到達するまで poll を
382
+ * 繰り返す utility。 requires_action は「final ではない」 が「client 側 action 待ち」
383
+ * なので、 これも終端扱いで返す (client 側で submitToolOutputs → 再度 pollUntilFinal
384
+ * を呼ぶ想定)。 real API では intervalMs で backoff するが、 mock は同期実行のため
385
+ * poll = 1 tick 進行 model で maxAttempts のみ意味を持つ。
386
+ */
387
+ async pollUntilFinal(runId, options = {}) {
388
+ const maxAttempts = options.maxAttempts ?? 20;
389
+ let attempts = 0;
390
+ let run = await this.poll(runId);
391
+ while (run.status === "queued" || run.status === "in_progress") {
392
+ attempts += 1;
393
+ if (attempts > maxAttempts) {
394
+ throw new Error(`pollUntilFinal exceeded ${maxAttempts} attempts for run ${runId}`);
395
+ }
396
+ run = await this.poll(runId);
397
+ }
398
+ return run;
399
+ }
400
+ /**
401
+ * submitToolOutputs — requires_action 中の Run に tool 実行結果を差し込む。 status
402
+ * を queued に戻し、 次 poll で handler が再度呼び出される (context.toolOutputs
403
+ * で結果参照可能)。 real API と同じ semantic。
404
+ */
405
+ submitToolOutputs(runId, params) {
406
+ const run = this.runs.get(runId);
407
+ if (!run) {
408
+ throw new Error(`unknown run id: ${runId}`);
409
+ }
410
+ if (run.status !== "requires_action") {
411
+ throw new Error(
412
+ `run ${runId} is not requires_action (current: ${run.status}), cannot submit tool outputs`
413
+ );
414
+ }
415
+ const { requiredAction: _dropped, ...rest } = run;
416
+ void _dropped;
417
+ const updated = {
418
+ ...rest,
419
+ status: "queued"
420
+ };
421
+ this.runs.set(runId, updated);
422
+ this.pendingToolOutputs.set(runId, params.toolOutputs);
423
+ return { ...updated };
424
+ }
425
+ /**
426
+ * cancel — queued / in_progress の Run を強制終了させる。 status は failed に倒す
427
+ * (real API は cancelled status を持つが v0.1 は failed に統合、 lastError.code =
428
+ * 'cancelled' で識別可能)。
429
+ */
430
+ cancel(runId) {
431
+ const run = this.runs.get(runId);
432
+ if (!run) {
433
+ throw new Error(`unknown run id: ${runId}`);
434
+ }
435
+ if (run.status === "completed" || run.status === "failed") {
436
+ return { ...run };
437
+ }
438
+ const { requiredAction: _dropped, ...rest } = run;
439
+ void _dropped;
440
+ const updated = {
441
+ ...rest,
442
+ status: "failed",
443
+ failedAt: Date.now(),
444
+ lastError: { code: "cancelled", message: "run cancelled by client" }
445
+ };
446
+ this.runs.set(runId, updated);
447
+ return { ...updated };
448
+ }
449
+ /** run 参照 (test / debug 用)。 */
450
+ getRun(id) {
451
+ const run = this.runs.get(id);
452
+ return run ? { ...run } : void 0;
453
+ }
454
+ // ---- internal helpers ----------------------------------------------
455
+ pendingToolOutputs = /* @__PURE__ */ new Map();
456
+ async executeRun(run) {
457
+ const thread = this.threads.get(run.threadId);
458
+ const handler = this.handlers.get(run.assistantId);
459
+ if (!thread || !handler) {
460
+ throw new Error(`run ${run.id} references missing thread or handler`);
461
+ }
462
+ const toolOutputs = this.pendingToolOutputs.get(run.id);
463
+ let result;
464
+ try {
465
+ const ctx = toolOutputs !== void 0 ? {
466
+ thread: [...thread.messages],
467
+ runId: run.id,
468
+ assistantId: run.assistantId,
469
+ toolOutputs
470
+ } : {
471
+ thread: [...thread.messages],
472
+ runId: run.id,
473
+ assistantId: run.assistantId
474
+ };
475
+ result = await handler(ctx);
476
+ } catch (err) {
477
+ const message = err instanceof Error ? err.message : String(err);
478
+ const updated2 = {
479
+ ...run,
480
+ status: "failed",
481
+ failedAt: Date.now(),
482
+ lastError: { code: "handler_error", message }
483
+ };
484
+ this.runs.set(run.id, updated2);
485
+ this.pendingToolOutputs.delete(run.id);
486
+ return { ...updated2 };
487
+ }
488
+ this.pendingToolOutputs.delete(run.id);
489
+ if (result.kind === "message") {
490
+ this.addMessage(run.threadId, { role: "assistant", content: result.content });
491
+ const updated2 = {
492
+ ...run,
493
+ status: "completed",
494
+ completedAt: Date.now()
495
+ };
496
+ this.runs.set(run.id, updated2);
497
+ return { ...updated2 };
498
+ }
499
+ const updated = {
500
+ ...run,
501
+ status: "requires_action",
502
+ requiredAction: { type: "submit_tool_outputs", toolCalls: result.toolCalls }
503
+ };
504
+ this.runs.set(run.id, updated);
505
+ return { ...updated };
506
+ }
507
+ mintId(kind) {
508
+ const id = `${this.idSeed}_${kind}_${this.nextId}`;
509
+ this.nextId += 1;
510
+ return id;
511
+ }
512
+ };
513
+ function toolCall(params) {
514
+ return {
515
+ id: params.id,
516
+ type: "function",
517
+ function: {
518
+ name: params.name,
519
+ arguments: JSON.stringify(params.arguments)
520
+ }
521
+ };
522
+ }
523
+ // Annotate the CommonJS export names for ESM import in node:
524
+ 0 && (module.exports = {
525
+ AssistantsClient,
526
+ CompiledGraph,
527
+ DEFAULT_MAX_STEPS,
528
+ END,
529
+ GraphCompileError,
530
+ MaxStepsExceededError,
531
+ START,
532
+ StateGraph,
533
+ StateMachine,
534
+ toolCall
535
+ });
536
+ //# sourceMappingURL=index.cjs.map