kernl 0.2.0 → 0.6.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/.turbo/turbo-build.log +4 -5
- package/.turbo/turbo-check-types.log +4 -0
- package/CHANGELOG.md +147 -0
- package/LICENSE +1 -1
- package/dist/agent/__tests__/concurrency.test.d.ts +2 -0
- package/dist/agent/__tests__/concurrency.test.d.ts.map +1 -0
- package/dist/agent/__tests__/concurrency.test.js +152 -0
- package/dist/agent/__tests__/run.test.d.ts +2 -0
- package/dist/agent/__tests__/run.test.d.ts.map +1 -0
- package/dist/agent/__tests__/run.test.js +357 -0
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent.d.ts +32 -9
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +102 -14
- package/dist/api/__tests__/cursor-page.test.d.ts +2 -0
- package/dist/api/__tests__/cursor-page.test.d.ts.map +1 -0
- package/dist/api/__tests__/cursor-page.test.js +414 -0
- package/dist/api/__tests__/offset-page.test.d.ts +2 -0
- package/dist/api/__tests__/offset-page.test.d.ts.map +1 -0
- package/dist/api/__tests__/offset-page.test.js +510 -0
- package/dist/api/__tests__/threads.test.d.ts +2 -0
- package/dist/api/__tests__/threads.test.d.ts.map +1 -0
- package/dist/api/__tests__/threads.test.js +338 -0
- package/dist/api/models/index.d.ts +2 -0
- package/dist/api/models/index.d.ts.map +1 -0
- package/dist/api/models/thread.d.ts +120 -0
- package/dist/api/models/thread.d.ts.map +1 -0
- package/dist/api/pagination/base.d.ts +48 -0
- package/dist/api/pagination/base.d.ts.map +1 -0
- package/dist/api/pagination/base.js +45 -0
- package/dist/api/pagination/cursor.d.ts +44 -0
- package/dist/api/pagination/cursor.d.ts.map +1 -0
- package/dist/api/pagination/cursor.js +52 -0
- package/dist/api/pagination/offset.d.ts +42 -0
- package/dist/api/pagination/offset.d.ts.map +1 -0
- package/dist/api/pagination/offset.js +55 -0
- package/dist/api/resources/threads/events.d.ts +21 -0
- package/dist/api/resources/threads/events.d.ts.map +1 -0
- package/dist/api/resources/threads/events.js +24 -0
- package/dist/api/resources/threads/index.d.ts +4 -0
- package/dist/api/resources/threads/index.d.ts.map +1 -0
- package/dist/api/resources/threads/index.js +2 -0
- package/dist/api/resources/threads/threads.d.ts +57 -0
- package/dist/api/resources/threads/threads.d.ts.map +1 -0
- package/dist/api/resources/threads/threads.js +199 -0
- package/dist/api/resources/threads/types.d.ts +123 -0
- package/dist/api/resources/threads/types.d.ts.map +1 -0
- package/dist/api/resources/threads/utils.d.ts +18 -0
- package/dist/api/resources/threads/utils.d.ts.map +1 -0
- package/dist/api/resources/threads/utils.js +78 -0
- package/dist/context.d.ts +5 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +6 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/internal.d.ts +4 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +2 -0
- package/dist/kernl/index.d.ts +3 -0
- package/dist/kernl/index.d.ts.map +1 -0
- package/dist/kernl/index.js +2 -0
- package/dist/kernl/kernl.d.ts +64 -0
- package/dist/kernl/kernl.d.ts.map +1 -0
- package/dist/kernl/kernl.js +116 -0
- package/dist/kernl/threads.d.ts +110 -0
- package/dist/kernl/threads.d.ts.map +1 -0
- package/dist/kernl/threads.js +126 -0
- package/dist/kernl.d.ts +22 -6
- package/dist/kernl.d.ts.map +1 -1
- package/dist/kernl.js +73 -10
- package/dist/lib/env.d.ts +3 -3
- package/dist/lib/env.js +1 -1
- package/dist/mcp/__tests__/integration.test.js +8 -8
- package/dist/mcp/__tests__/utils.test.js +6 -6
- package/dist/mcp/http.d.ts +1 -1
- package/dist/mcp/http.d.ts.map +1 -1
- package/dist/mcp/http.js +9 -9
- package/dist/mcp/sse.d.ts +1 -1
- package/dist/mcp/sse.d.ts.map +1 -1
- package/dist/mcp/sse.js +7 -7
- package/dist/mcp/utils.d.ts +1 -1
- package/dist/mcp/utils.d.ts.map +1 -1
- package/dist/mcp/utils.js +4 -5
- package/dist/storage/__tests__/in-memory.test.d.ts +2 -0
- package/dist/storage/__tests__/in-memory.test.d.ts.map +1 -0
- package/dist/storage/__tests__/in-memory.test.js +455 -0
- package/dist/storage/base.d.ts +64 -0
- package/dist/storage/base.d.ts.map +1 -0
- package/dist/storage/base.js +4 -0
- package/dist/storage/in-memory.d.ts +62 -0
- package/dist/storage/in-memory.d.ts.map +1 -0
- package/dist/storage/in-memory.js +283 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +7 -0
- package/dist/storage/thread.d.ts +123 -0
- package/dist/storage/thread.d.ts.map +1 -0
- package/dist/storage/thread.js +4 -0
- package/dist/task.d.ts +5 -3
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +10 -8
- package/dist/thread/__tests__/fixtures/mock-model.d.ts +1 -2
- package/dist/thread/__tests__/fixtures/mock-model.d.ts.map +1 -1
- package/dist/thread/__tests__/integration.test.js +73 -5
- package/dist/thread/__tests__/namespace.test.d.ts +2 -0
- package/dist/thread/__tests__/namespace.test.d.ts.map +1 -0
- package/dist/thread/__tests__/namespace.test.js +131 -0
- package/dist/thread/__tests__/thread-persistence.test.d.ts +2 -0
- package/dist/thread/__tests__/thread-persistence.test.d.ts.map +1 -0
- package/dist/thread/__tests__/thread-persistence.test.js +351 -0
- package/dist/thread/__tests__/thread.test.js +49 -51
- package/dist/thread/thread.d.ts +70 -18
- package/dist/thread/thread.d.ts.map +1 -1
- package/dist/thread/thread.js +211 -73
- package/dist/thread/utils.d.ts +36 -8
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/utils.js +52 -8
- package/dist/tool/__tests__/fixtures.js +1 -1
- package/dist/tool/__tests__/toolkit.test.js +15 -12
- package/dist/tool/tool.js +3 -3
- package/dist/types/kernl.d.ts +42 -0
- package/dist/types/kernl.d.ts.map +1 -0
- package/dist/types/thread.d.ts +108 -22
- package/dist/types/thread.d.ts.map +1 -1
- package/dist/types/thread.js +12 -0
- package/package.json +11 -7
- package/src/agent/__tests__/concurrency.test.ts +194 -0
- package/src/agent/__tests__/run.test.ts +441 -0
- package/src/agent/index.ts +0 -0
- package/src/agent.ts +141 -24
- package/src/api/__tests__/cursor-page.test.ts +512 -0
- package/src/api/__tests__/offset-page.test.ts +624 -0
- package/src/api/__tests__/threads.test.ts +415 -0
- package/src/api/models/index.ts +6 -0
- package/src/api/models/thread.ts +138 -0
- package/src/api/pagination/base.ts +79 -0
- package/src/api/pagination/cursor.ts +86 -0
- package/src/api/pagination/offset.ts +89 -0
- package/src/api/resources/threads/events.ts +26 -0
- package/src/api/resources/threads/index.ts +9 -0
- package/src/api/resources/threads/threads.ts +256 -0
- package/src/api/resources/threads/types.ts +143 -0
- package/src/api/resources/threads/utils.ts +104 -0
- package/src/context.ts +10 -1
- package/src/index.ts +49 -1
- package/src/internal.ts +15 -0
- package/src/kernl.ts +86 -17
- package/src/mcp/__tests__/integration.test.ts +8 -9
- package/src/mcp/__tests__/utils.test.ts +6 -6
- package/src/mcp/http.ts +9 -9
- package/src/mcp/sse.ts +7 -7
- package/src/mcp/utils.ts +6 -5
- package/src/storage/__tests__/in-memory.test.ts +534 -0
- package/src/storage/base.ts +77 -0
- package/src/storage/in-memory.ts +372 -0
- package/src/storage/index.ts +21 -0
- package/src/storage/thread.ts +141 -0
- package/src/task.ts +12 -10
- package/src/thread/__tests__/fixtures/mock-model.ts +2 -4
- package/src/thread/__tests__/integration.test.ts +111 -10
- package/src/thread/__tests__/namespace.test.ts +158 -0
- package/src/thread/__tests__/thread-persistence.test.ts +367 -0
- package/src/thread/__tests__/thread.test.ts +52 -54
- package/src/thread/thread.ts +247 -96
- package/src/thread/utils.ts +76 -13
- package/src/tool/__tests__/fixtures.ts +1 -1
- package/src/tool/__tests__/toolkit.test.ts +15 -12
- package/src/tool/tool.ts +3 -3
- package/src/types/kernl.ts +51 -0
- package/src/types/thread.ts +139 -25
- package/vitest.config.ts +1 -0
- package/dist/env.d.ts +0 -45
- package/dist/env.d.ts.map +0 -1
- package/dist/env.js +0 -31
- package/dist/error.d.ts +0 -1
- package/dist/error.d.ts.map +0 -1
- package/dist/kernel.d.ts +0 -7
- package/dist/kernel.d.ts.map +0 -1
- package/dist/kernel.js +0 -7
- package/dist/lib/serde/__tests__/codec.test.d.ts +0 -2
- package/dist/lib/serde/__tests__/codec.test.d.ts.map +0 -1
- package/dist/lib/serde/__tests__/codec.test.js +0 -75
- package/dist/lib/serde/codec.d.ts +0 -12
- package/dist/lib/serde/codec.d.ts.map +0 -1
- package/dist/lib/serde/codec.js +0 -54
- package/dist/lib/serde/thread.d.ts +0 -1
- package/dist/lib/serde/thread.d.ts.map +0 -1
- package/dist/lib/serde/thread.js +0 -172
- package/dist/lib/serde/tool.d.ts +0 -36
- package/dist/lib/serde/tool.d.ts.map +0 -1
- package/dist/lib/utils.d.ts +0 -19
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/utils.js +0 -41
- package/dist/logger.d.ts +0 -36
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -43
- package/dist/mcp/__tests__/fixtures/echo-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/echo-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/echo-server.js +0 -92
- package/dist/mcp/__tests__/fixtures/math-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/math-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/math-server.js +0 -98
- package/dist/mcp/__tests__/fixtures/test-server.d.ts +0 -3
- package/dist/mcp/__tests__/fixtures/test-server.d.ts.map +0 -1
- package/dist/mcp/__tests__/fixtures/test-server.js +0 -163
- package/dist/mcp/__tests__/test-utils.d.ts +0 -17
- package/dist/mcp/__tests__/test-utils.d.ts.map +0 -1
- package/dist/mcp/__tests__/test-utils.js +0 -42
- package/dist/mcp/node.d.ts +0 -60
- package/dist/mcp/node.d.ts.map +0 -1
- package/dist/mcp/node.js +0 -297
- package/dist/model.d.ts +0 -175
- package/dist/model.d.ts.map +0 -1
- package/dist/providers/ai.d.ts +0 -1
- package/dist/providers/ai.d.ts.map +0 -1
- package/dist/providers/ai.js +0 -1
- package/dist/providers/default.d.ts +0 -16
- package/dist/providers/default.d.ts.map +0 -1
- package/dist/providers/default.js +0 -17
- package/dist/providers/registry.d.ts +0 -1
- package/dist/providers/registry.d.ts.map +0 -1
- package/dist/providers/registry.js +0 -1
- package/dist/sched/scheduler.d.ts +0 -20
- package/dist/sched/scheduler.d.ts.map +0 -1
- package/dist/sched/task.d.ts +0 -92
- package/dist/sched/task.d.ts.map +0 -1
- package/dist/sched/task.js +0 -102
- package/dist/serde/__tests__/codec.test.d.ts +0 -2
- package/dist/serde/__tests__/codec.test.d.ts.map +0 -1
- package/dist/serde/__tests__/codec.test.js +0 -75
- package/dist/serde/codec.d.ts +0 -12
- package/dist/serde/codec.d.ts.map +0 -1
- package/dist/serde/codec.js +0 -54
- package/dist/serde/json.d.ts +0 -8
- package/dist/serde/json.d.ts.map +0 -1
- package/dist/serde/json.js +0 -13
- package/dist/serde/thread.d.ts +0 -687
- package/dist/serde/thread.d.ts.map +0 -1
- package/dist/serde/thread.js +0 -158
- package/dist/serde/tool.d.ts +0 -36
- package/dist/serde/tool.d.ts.map +0 -1
- package/dist/session.d.ts +0 -1
- package/dist/session.d.ts.map +0 -1
- package/dist/session.js +0 -1
- package/dist/thread/__tests__/stream.test.d.ts +0 -2
- package/dist/thread/__tests__/stream.test.d.ts.map +0 -1
- package/dist/thread/__tests__/stream.test.js +0 -244
- package/dist/tool/mcp.d.ts +0 -75
- package/dist/tool/mcp.d.ts.map +0 -1
- package/dist/tool/mcp.js +0 -111
- package/dist/tools.d.ts +0 -362
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -220
- package/dist/types/proto.d.ts +0 -1551
- package/dist/types/proto.d.ts.map +0 -1
- package/dist/types/proto.js +0 -531
- package/dist/usage.d.ts +0 -43
- package/dist/usage.d.ts.map +0 -1
- package/dist/usage.js +0 -61
- package/src/lib/serde/thread.ts +0 -188
- /package/dist/{error.js → agent/index.js} +0 -0
- /package/dist/{lib/serde/tool.js → api/models/index.js} +0 -0
- /package/dist/{model.js → api/models/thread.js} +0 -0
- /package/dist/{sched/scheduler.js → api/resources/threads/types.js} +0 -0
- /package/dist/{serde/tool.js → types/kernl.js} +0 -0
package/dist/thread/thread.js
CHANGED
|
@@ -1,48 +1,111 @@
|
|
|
1
1
|
import assert from "assert";
|
|
2
2
|
import { Context } from "../context";
|
|
3
|
-
import {
|
|
3
|
+
import { logger } from "../lib/logger";
|
|
4
|
+
import { FAILED, RUNNING, STOPPED, message, } from "@kernl-sdk/protocol";
|
|
4
5
|
import { randomID, filter } from "@kernl-sdk/shared/lib";
|
|
5
|
-
import {
|
|
6
|
+
import { tevent, notDelta, getIntentions, getFinalResponse, parseFinalResponse, } from "./utils";
|
|
6
7
|
/**
|
|
7
8
|
* A thread drives the execution loop for an agent.
|
|
9
|
+
*
|
|
10
|
+
* Ground principles:
|
|
11
|
+
*
|
|
12
|
+
* 1) Event log is source of truth.
|
|
13
|
+
* - Persistent storage (e.g. Postgres) is treated as an append-only per-thread log of `ThreadEvent`s:
|
|
14
|
+
* monotonic `seq`, no gaps, no updates/deletes.
|
|
15
|
+
* - `Thread.state`, `tick`, etc. are projections of that log, not an alternative source of truth.
|
|
16
|
+
*
|
|
17
|
+
* 2) Single writer per thread.
|
|
18
|
+
* - At most one executor is allowed for a given `tid` at a time.
|
|
19
|
+
* - Callers are responsible for enforcing this (e.g. locking/versioning) so two processes cannot
|
|
20
|
+
* interleave or race on `seq` or state.
|
|
21
|
+
*
|
|
22
|
+
* 3) Persist before use / observation.
|
|
23
|
+
* - Before an event can:
|
|
24
|
+
* - influence a future tick (i.e. be part of `history` fed back into the model), or
|
|
25
|
+
* - be considered “delivered” to a client,
|
|
26
|
+
* it SHOULD be durably written to storage when storage is configured.
|
|
27
|
+
*
|
|
28
|
+
* 4) Transaction boundaries match semantic steps.
|
|
29
|
+
* - The intended strategy is to buffer within a tick, then atomically persist all new events + state
|
|
30
|
+
* at the end of `tick()`.
|
|
31
|
+
* - After a crash, you only ever see whole ticks or none, never half a tick, from the store’s
|
|
32
|
+
* point of view.
|
|
33
|
+
*
|
|
34
|
+
* 5) Recovery is replay.
|
|
35
|
+
* - On restart, callers rebuild a `Thread` from the stored event log (plus optional snapshots).
|
|
36
|
+
* - Any incomplete tick or pending tool call is handled by a clear, deterministic policy at a
|
|
37
|
+
* higher layer (e.g. re-run, mark failed, or require manual intervention).
|
|
38
|
+
*
|
|
39
|
+
* On storage failures:
|
|
40
|
+
*
|
|
41
|
+
* “If storage is configured, it is authoritative” → fail hard on persist errors rather than
|
|
42
|
+
* treating persistence as best-effort.
|
|
43
|
+
*
|
|
44
|
+
* If a storage implementation is present, `persist(...)` is expected to throw on failure, and
|
|
45
|
+
* that error should bubble out of `_execute()` / `stream()` and stop the thread.
|
|
8
46
|
*/
|
|
9
47
|
export class Thread {
|
|
10
|
-
|
|
11
|
-
|
|
48
|
+
tid;
|
|
49
|
+
namespace;
|
|
12
50
|
agent;
|
|
13
51
|
context;
|
|
14
52
|
model; /* inherited from the agent unless specified */
|
|
15
53
|
parent; /* parent task which spawned this thread */
|
|
16
|
-
|
|
17
|
-
|
|
54
|
+
createdAt;
|
|
55
|
+
updatedAt;
|
|
56
|
+
metadata;
|
|
18
57
|
// readonly stats: ThreadMetrics;
|
|
19
58
|
/* state */
|
|
20
|
-
_tick;
|
|
59
|
+
_tick; /* number of LLM roundtrips */
|
|
60
|
+
_seq; /* monotonic event sequence */
|
|
21
61
|
state;
|
|
62
|
+
cpbuf; /* checkpoint buffer - events pending persistence */
|
|
63
|
+
persisted; /* indicates thread was hydrated from storage */
|
|
22
64
|
history;
|
|
23
65
|
abort;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
66
|
+
storage;
|
|
67
|
+
constructor(options) {
|
|
68
|
+
this.tid = options.tid ?? `tid_${randomID()}`;
|
|
69
|
+
this.namespace = options.namespace ?? "kernl";
|
|
70
|
+
this.agent = options.agent;
|
|
71
|
+
this.context =
|
|
72
|
+
options.context ?? new Context(this.namespace, {});
|
|
73
|
+
this.parent = options.task ?? null;
|
|
74
|
+
this.model = options.model ?? options.agent.model;
|
|
75
|
+
this.storage = options.storage;
|
|
76
|
+
this.createdAt = options.createdAt ?? new Date();
|
|
77
|
+
this.updatedAt = options.updatedAt ?? new Date();
|
|
78
|
+
this.metadata = options.metadata ?? null;
|
|
79
|
+
this._tick = options.tick ?? 0;
|
|
80
|
+
this._seq = -1;
|
|
81
|
+
this.state = options.state ?? STOPPED;
|
|
82
|
+
this.cpbuf = [];
|
|
83
|
+
this.persisted = options.persisted ?? false;
|
|
84
|
+
this.history = options.history ?? [];
|
|
85
|
+
// seek to latest seq (not persisted)
|
|
86
|
+
if (this.history.length > 0) {
|
|
87
|
+
this._seq = Math.max(...this.history.map((e) => e.seq));
|
|
88
|
+
}
|
|
89
|
+
// append initial input if provided (for new threads)
|
|
90
|
+
if (options.input && options.input.length > 0) {
|
|
91
|
+
this.append(...options.input);
|
|
92
|
+
}
|
|
36
93
|
}
|
|
37
94
|
/**
|
|
38
|
-
* Blocking execution
|
|
95
|
+
* Blocking execution - runs until terminal state or interruption
|
|
39
96
|
*/
|
|
40
97
|
async execute() {
|
|
41
98
|
for await (const _event of this.stream()) {
|
|
42
99
|
// just consume the stream (already in history in _execute())
|
|
43
100
|
}
|
|
44
|
-
//
|
|
45
|
-
const
|
|
101
|
+
// filter for language model items
|
|
102
|
+
const items = this.history
|
|
103
|
+
.filter((e) => e.kind !== "system")
|
|
104
|
+
.map((e) => {
|
|
105
|
+
const { tid, seq, timestamp, metadata, ...item } = e;
|
|
106
|
+
return item;
|
|
107
|
+
});
|
|
108
|
+
const text = getFinalResponse(items);
|
|
46
109
|
assert(text, "_execute continues until text !== null"); // (TODO): consider preventing infinite loops here
|
|
47
110
|
const parsed = parseFinalResponse(text, this.agent.responseType);
|
|
48
111
|
return { response: parsed, state: this.state };
|
|
@@ -56,6 +119,8 @@ export class Thread {
|
|
|
56
119
|
}
|
|
57
120
|
this.state = RUNNING;
|
|
58
121
|
this.abort = new AbortController();
|
|
122
|
+
await this.checkpoint(); /* c1: persist RUNNING state + initial input */
|
|
123
|
+
yield { kind: "stream-start" }; // always yield start immediately
|
|
59
124
|
try {
|
|
60
125
|
yield* this._execute();
|
|
61
126
|
}
|
|
@@ -65,57 +130,57 @@ export class Thread {
|
|
|
65
130
|
finally {
|
|
66
131
|
this.state = STOPPED;
|
|
67
132
|
this.abort = undefined;
|
|
133
|
+
await this.checkpoint(); /* c4: final checkpoint - persist STOPPED state */
|
|
68
134
|
}
|
|
69
135
|
}
|
|
70
136
|
/**
|
|
71
|
-
*
|
|
72
|
-
*/
|
|
73
|
-
cancel() {
|
|
74
|
-
this.abort?.abort();
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Append a new event to the thread history
|
|
78
|
-
*/
|
|
79
|
-
append(event) {
|
|
80
|
-
this.history.push(event);
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Main execution loop - always yields events, callers can propagate or discard (as in execute())
|
|
137
|
+
* Main execution loop - always yields events, callers can propagate or discard.
|
|
84
138
|
*
|
|
85
139
|
* NOTE: Streaming structured output deferred for now. Prioritizing correctness + simplicity,
|
|
86
140
|
* and unclear what use cases there would actually be for streaming a structured output (other than maybe gen UI).
|
|
87
141
|
*/
|
|
88
142
|
async *_execute() {
|
|
89
143
|
for (;;) {
|
|
144
|
+
let err = false;
|
|
90
145
|
if (this.abort?.signal.aborted) {
|
|
91
146
|
return;
|
|
92
147
|
}
|
|
93
148
|
const events = [];
|
|
94
149
|
for await (const e of this.tick()) {
|
|
150
|
+
if (e.kind === "error") {
|
|
151
|
+
err = true;
|
|
152
|
+
logger.error(e.error); // (TODO): onError callback in options
|
|
153
|
+
}
|
|
95
154
|
// we don't want deltas in the history
|
|
96
155
|
if (notDelta(e)) {
|
|
97
156
|
events.push(e);
|
|
98
|
-
this.
|
|
157
|
+
this.append(e);
|
|
99
158
|
}
|
|
100
159
|
yield e;
|
|
101
160
|
}
|
|
102
|
-
// if
|
|
161
|
+
// if an error event occurred → terminate
|
|
162
|
+
if (err) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// if model returns a message with no action intentions → terminal state
|
|
103
166
|
const intentions = getIntentions(events);
|
|
104
167
|
if (!intentions) {
|
|
105
168
|
const text = getFinalResponse(events);
|
|
106
169
|
if (!text)
|
|
107
170
|
continue; // run again, policy-dependent? (how to ensure no infinite loop here?)
|
|
171
|
+
await this.checkpoint(); /* c2: terminal tick - no tool calls */
|
|
108
172
|
// await this.agent.runOutputGuardails(context, state);
|
|
109
173
|
// this.kernl.emit("thread.terminated", context, output);
|
|
110
174
|
return;
|
|
111
175
|
}
|
|
112
|
-
// perform
|
|
176
|
+
// perform intended actions
|
|
113
177
|
const { actions, pendingApprovals } = await this.performActions(intentions);
|
|
114
|
-
// yield action events
|
|
178
|
+
// append + yield action events
|
|
115
179
|
for (const a of actions) {
|
|
116
|
-
this.
|
|
180
|
+
this.append(a);
|
|
117
181
|
yield a;
|
|
118
182
|
}
|
|
183
|
+
await this.checkpoint(); /* c3: tick complete */
|
|
119
184
|
if (pendingApprovals.length > 0) {
|
|
120
185
|
// publish a batch approval request containing all of them
|
|
121
186
|
//
|
|
@@ -138,22 +203,100 @@ export class Thread {
|
|
|
138
203
|
// (TODO): check limits (if this._tick > this.limits.maxTicks)
|
|
139
204
|
// (TODO): run input guardrails on first tick (if this._tick === 1)
|
|
140
205
|
const req = await this.prepareModelRequest(this.history);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
206
|
+
try {
|
|
207
|
+
if (this.model.stream) {
|
|
208
|
+
const stream = this.model.stream(req);
|
|
209
|
+
for await (const event of stream) {
|
|
210
|
+
yield event; // [text-delta, tool-call, message, reasoning, ...]
|
|
211
|
+
}
|
|
146
212
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
213
|
+
else {
|
|
214
|
+
// fallback: blocking generate, yield events as batch
|
|
215
|
+
const res = await this.model.generate(req);
|
|
216
|
+
for (const event of res.content) {
|
|
217
|
+
yield event;
|
|
218
|
+
}
|
|
219
|
+
// (TODO): this.stats.usage.add(res.usage)
|
|
153
220
|
}
|
|
154
|
-
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
yield {
|
|
224
|
+
kind: "error",
|
|
225
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
226
|
+
};
|
|
155
227
|
}
|
|
156
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Persist current thread state to storage.
|
|
231
|
+
*
|
|
232
|
+
* - If storage is configured, it is authoritative - failures throw and halt execution.
|
|
233
|
+
* - No-op if storage is not configured.
|
|
234
|
+
*/
|
|
235
|
+
async checkpoint() {
|
|
236
|
+
if (!this.storage) {
|
|
237
|
+
logger.warn("thread: storage is not configured, thread will not be persisted");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// insert thread record on first persist for new threads
|
|
241
|
+
if (!this.persisted) {
|
|
242
|
+
await this.storage.insert({
|
|
243
|
+
id: this.tid,
|
|
244
|
+
namespace: this.namespace,
|
|
245
|
+
agentId: this.agent.id,
|
|
246
|
+
parentTaskId: this.parent?.id ?? null,
|
|
247
|
+
model: `${this.model.provider}/${this.model.modelId}`,
|
|
248
|
+
context: this.context.context,
|
|
249
|
+
tick: this._tick,
|
|
250
|
+
state: this.state,
|
|
251
|
+
metadata: this.metadata,
|
|
252
|
+
});
|
|
253
|
+
this.persisted = true;
|
|
254
|
+
}
|
|
255
|
+
// append + drain events from checkpoint buffer
|
|
256
|
+
if (this.cpbuf.length > 0) {
|
|
257
|
+
await this.storage.append(this.cpbuf);
|
|
258
|
+
this.cpbuf = [];
|
|
259
|
+
}
|
|
260
|
+
// update thread state
|
|
261
|
+
await this.storage.update(this.tid, {
|
|
262
|
+
state: this.state,
|
|
263
|
+
tick: this._tick,
|
|
264
|
+
context: this.context,
|
|
265
|
+
metadata: this.metadata,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Append one or more items to history + enrich w/ runtime headers.
|
|
270
|
+
*
|
|
271
|
+
* Core rule:
|
|
272
|
+
*
|
|
273
|
+
* > An event becomes a ThreadEvent (and gets seq/timestamp) exactly when it is appended to history. <
|
|
274
|
+
*/
|
|
275
|
+
append(...items) {
|
|
276
|
+
const events = [];
|
|
277
|
+
for (const item of items) {
|
|
278
|
+
const seq = ++this._seq;
|
|
279
|
+
const e = tevent({
|
|
280
|
+
tid: this.tid,
|
|
281
|
+
seq,
|
|
282
|
+
kind: item.kind,
|
|
283
|
+
data: item,
|
|
284
|
+
});
|
|
285
|
+
this.history.push(e);
|
|
286
|
+
this.cpbuf.push(e);
|
|
287
|
+
events.push(e);
|
|
288
|
+
}
|
|
289
|
+
return events;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Cancel the running thread
|
|
293
|
+
*/
|
|
294
|
+
cancel() {
|
|
295
|
+
this.abort?.abort();
|
|
296
|
+
}
|
|
297
|
+
// ----------------------------
|
|
298
|
+
// utils
|
|
299
|
+
// ----------------------------
|
|
157
300
|
/**
|
|
158
301
|
* Perform the actions returned by the model
|
|
159
302
|
*/
|
|
@@ -179,10 +322,8 @@ export class Thread {
|
|
|
179
322
|
e.state === "requires_approval" // (TODO): fix this
|
|
180
323
|
) {
|
|
181
324
|
// Find the original tool call for this pending approval
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
pendingApprovals.push(originalCall);
|
|
185
|
-
}
|
|
325
|
+
const call = intentions.toolCalls.find((c) => c.callId === e.callId);
|
|
326
|
+
call && pendingApprovals.push(call);
|
|
186
327
|
}
|
|
187
328
|
else {
|
|
188
329
|
actions.push(e);
|
|
@@ -209,7 +350,7 @@ export class Thread {
|
|
|
209
350
|
assert(tool.type === "function", `Tool ${call.id} is a hosted tool and should not be executed locally`);
|
|
210
351
|
// (TMP) - passing the approval status through the context until actions system
|
|
211
352
|
// is refined
|
|
212
|
-
const ctx = new Context(this.context.context);
|
|
353
|
+
const ctx = new Context(this.namespace, this.context.context);
|
|
213
354
|
ctx.approve(call.callId); // mark this call as approved
|
|
214
355
|
const res = await tool.invoke(ctx, call.arguments, call.callId);
|
|
215
356
|
return {
|
|
@@ -222,7 +363,6 @@ export class Thread {
|
|
|
222
363
|
};
|
|
223
364
|
}
|
|
224
365
|
catch (error) {
|
|
225
|
-
// Handles both tool not found AND any execution errors
|
|
226
366
|
return {
|
|
227
367
|
kind: "tool-result",
|
|
228
368
|
callId: call.callId,
|
|
@@ -241,27 +381,25 @@ export class Thread {
|
|
|
241
381
|
let settings = {
|
|
242
382
|
...this.agent.modelSettings,
|
|
243
383
|
};
|
|
244
|
-
//
|
|
384
|
+
// (TODO): what do we want to do with this?
|
|
245
385
|
// settings = maybeResetToolChoice(this.agent, this.state.toolUse, settings);
|
|
246
386
|
const system = await this.agent.instructions(this.context);
|
|
387
|
+
// filter for model items + strip event headers
|
|
388
|
+
const items = history
|
|
389
|
+
.filter((e) => e.kind !== "system") // system events are not sent to model
|
|
390
|
+
.map((event) => {
|
|
391
|
+
const { id, tid, seq, timestamp, metadata, ...item } = event;
|
|
392
|
+
return item;
|
|
393
|
+
});
|
|
247
394
|
const input = system
|
|
248
|
-
? [
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
kind: "message",
|
|
252
|
-
id: randomID(),
|
|
253
|
-
role: "system",
|
|
254
|
-
content: [{ kind: "text", text: system }],
|
|
255
|
-
},
|
|
256
|
-
...history, // (TODO): filter for LanguageModelItem specifically - there may be other thread events
|
|
257
|
-
]
|
|
258
|
-
: history;
|
|
259
|
-
// TODO: apply custom input filters - arguably want global + agent-scoped -> apply in a middleware-like chain
|
|
395
|
+
? [message({ role: "system", text: system }), ...items]
|
|
396
|
+
: items;
|
|
397
|
+
// (TODO): apply custom input filters - arguably want global + agent-scoped -> apply in a middleware-like chain
|
|
260
398
|
// const filtered = await applyInputFilters(inputWithSystem, context);
|
|
261
399
|
const filtered = input;
|
|
262
400
|
// serialize action repertoire
|
|
263
|
-
const
|
|
264
|
-
const enabled = await filter(
|
|
401
|
+
const all = await this.agent.tools(this.context);
|
|
402
|
+
const enabled = await filter(all, async (tool) => await tool.isEnabled(this.context, this.agent));
|
|
265
403
|
const tools = enabled.map((tool) => tool.serialize());
|
|
266
404
|
return {
|
|
267
405
|
input: filtered,
|
package/dist/thread/utils.d.ts
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
import type { ResolvedAgentResponse } from "../guardrail";
|
|
2
|
-
import { ToolCall } from "@kernl-sdk/protocol";
|
|
2
|
+
import { ToolCall, LanguageModelItem } from "@kernl-sdk/protocol";
|
|
3
3
|
import type { AgentResponseType } from "../types/agent";
|
|
4
|
-
import type { ThreadEvent, ThreadStreamEvent, ActionSet } from "../types/thread";
|
|
4
|
+
import type { ThreadEvent, ThreadStreamEvent, ActionSet, PublicThreadEvent } from "../types/thread";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Create a ThreadEvent from a LanguageModelItem with thread metadata.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* tevent({
|
|
11
|
+
* kind: "message",
|
|
12
|
+
* seq: 0,
|
|
13
|
+
* tid: "tid_123",
|
|
14
|
+
* data: message({role: "user", text: "hello"}),
|
|
15
|
+
* })
|
|
16
|
+
* // → {kind: "message", role: "user", content: [...], id: "message:msg_xyz", tid: "tid_123", seq: 0, timestamp: Date}
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function tevent(event: {
|
|
20
|
+
seq: number;
|
|
21
|
+
tid: string;
|
|
22
|
+
kind: ThreadEvent["kind"];
|
|
23
|
+
data: LanguageModelItem | null;
|
|
24
|
+
id?: string;
|
|
25
|
+
timestamp?: Date;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
}): ThreadEvent;
|
|
28
|
+
/**
|
|
29
|
+
* Check if an event is a tool call
|
|
7
30
|
*/
|
|
8
|
-
export declare function isActionIntention(event:
|
|
31
|
+
export declare function isActionIntention(event: LanguageModelItem): event is ToolCall;
|
|
9
32
|
/**
|
|
10
33
|
* Extract action intentions from a list of events.
|
|
11
34
|
* Returns ActionSet if there are any tool calls, null otherwise.
|
|
12
35
|
*/
|
|
13
|
-
export declare function getIntentions(events:
|
|
36
|
+
export declare function getIntentions(events: LanguageModelItem[]): ActionSet | null;
|
|
14
37
|
/**
|
|
15
38
|
* Check if an event is NOT a delta/start/end event (i.e., a complete item).
|
|
16
39
|
* Returns true for complete items: Message, Reasoning, ToolCall, ToolResult
|
|
17
40
|
*/
|
|
18
|
-
export declare function notDelta(event: ThreadStreamEvent): event is
|
|
41
|
+
export declare function notDelta(event: ThreadStreamEvent): event is LanguageModelItem;
|
|
42
|
+
/**
|
|
43
|
+
* Check if an event is public/client-facing (not internal).
|
|
44
|
+
* Filters out internal system events that clients don't need.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isPublicEvent(event: ThreadEvent): event is PublicThreadEvent;
|
|
19
47
|
/**
|
|
20
|
-
* Extract the final text response from a list of
|
|
48
|
+
* Extract the final text response from a list of items.
|
|
21
49
|
* Returns null if no assistant message with text content is found.
|
|
22
50
|
*/
|
|
23
|
-
export declare function getFinalResponse(
|
|
51
|
+
export declare function getFinalResponse(items: LanguageModelItem[]): string | null;
|
|
24
52
|
/**
|
|
25
53
|
* (TODO): This should run through the language model's native structured output (if avail)
|
|
26
54
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/thread/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/thread/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAIzD,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAIlE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EACjB,SAAS,EACT,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,GAAG,WAAW,CAad;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,IAAI,QAAQ,CAE7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG,SAAS,GAAG,IAAI,CAG3E;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,IAAI,iBAAiB,CAY7E;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,KAAK,IAAI,iBAAiB,CAc5E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,MAAM,GAAG,IAAI,CAc1E;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,SAAS,iBAAiB,EACpE,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,SAAS,GACtB,qBAAqB,CAAC,SAAS,CAAC,CAsBlC"}
|
package/dist/thread/utils.js
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
1
|
/* lib */
|
|
2
|
-
import { json } from "@kernl-sdk/shared/lib";
|
|
2
|
+
import { json, randomID } from "@kernl-sdk/shared/lib";
|
|
3
3
|
import { ModelBehaviorError } from "../lib/error";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Create a ThreadEvent from a LanguageModelItem with thread metadata.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* tevent({
|
|
10
|
+
* kind: "message",
|
|
11
|
+
* seq: 0,
|
|
12
|
+
* tid: "tid_123",
|
|
13
|
+
* data: message({role: "user", text: "hello"}),
|
|
14
|
+
* })
|
|
15
|
+
* // → {kind: "message", role: "user", content: [...], id: "message:msg_xyz", tid: "tid_123", seq: 0, timestamp: Date}
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function tevent(event) {
|
|
19
|
+
const iid = event.data ? event.data.id : undefined;
|
|
20
|
+
const defaultId = iid ? `${event.kind}:${iid}` : randomID();
|
|
21
|
+
return {
|
|
22
|
+
...(event.data || {}),
|
|
23
|
+
kind: event.kind,
|
|
24
|
+
id: event.id ?? defaultId,
|
|
25
|
+
tid: event.tid,
|
|
26
|
+
seq: event.seq,
|
|
27
|
+
timestamp: event.timestamp ?? new Date(),
|
|
28
|
+
metadata: event.metadata ?? {},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if an event is a tool call
|
|
6
33
|
*/
|
|
7
34
|
export function isActionIntention(event) {
|
|
8
35
|
return event.kind === "tool-call";
|
|
@@ -32,16 +59,33 @@ export function notDelta(event) {
|
|
|
32
59
|
}
|
|
33
60
|
}
|
|
34
61
|
/**
|
|
35
|
-
*
|
|
62
|
+
* Check if an event is public/client-facing (not internal).
|
|
63
|
+
* Filters out internal system events that clients don't need.
|
|
64
|
+
*/
|
|
65
|
+
export function isPublicEvent(event) {
|
|
66
|
+
switch (event.kind) {
|
|
67
|
+
case "message":
|
|
68
|
+
case "reasoning":
|
|
69
|
+
case "tool-call":
|
|
70
|
+
case "tool-result":
|
|
71
|
+
return true;
|
|
72
|
+
case "system":
|
|
73
|
+
return false;
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extract the final text response from a list of items.
|
|
36
80
|
* Returns null if no assistant message with text content is found.
|
|
37
81
|
*/
|
|
38
|
-
export function getFinalResponse(
|
|
82
|
+
export function getFinalResponse(items) {
|
|
39
83
|
// Scan backwards for the last assistant message
|
|
40
|
-
for (let i =
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
84
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
85
|
+
const item = items[i];
|
|
86
|
+
if (item.kind === "message" && item.role === "assistant") {
|
|
43
87
|
// Extract text from content parts
|
|
44
|
-
for (const part of
|
|
88
|
+
for (const part of item.content) {
|
|
45
89
|
if (part.kind === "text") {
|
|
46
90
|
return part.text;
|
|
47
91
|
}
|
|
@@ -5,7 +5,7 @@ import { tool, HostedTool } from "../tool";
|
|
|
5
5
|
* Create a minimal mock context for testing
|
|
6
6
|
*/
|
|
7
7
|
export const mockContext = (data) => {
|
|
8
|
-
return new Context(data ?? {});
|
|
8
|
+
return new Context("test-namespace", data ?? {});
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
11
|
* Simple string tool with no parameters
|
|
@@ -83,22 +83,22 @@ describe("FunctionToolkit", () => {
|
|
|
83
83
|
const toolkit = new FunctionToolkit({ id: "test", tools: [simpleStringTool] });
|
|
84
84
|
const serialized = (await toolkit.list()).map((tool) => tool.serialize());
|
|
85
85
|
expect(serialized).toHaveLength(1);
|
|
86
|
-
expect(serialized[0]).
|
|
87
|
-
|
|
88
|
-
name: simpleStringTool.
|
|
86
|
+
expect(serialized[0]).toMatchObject({
|
|
87
|
+
kind: "function",
|
|
88
|
+
name: simpleStringTool.id,
|
|
89
89
|
description: simpleStringTool.description,
|
|
90
|
-
parameters: simpleStringTool.parameters,
|
|
91
90
|
});
|
|
91
|
+
expect(serialized[0].parameters).toBeDefined();
|
|
92
92
|
});
|
|
93
93
|
it("should serialize hosted tools correctly", async () => {
|
|
94
94
|
const toolkit = new FunctionToolkit({ id: "test", tools: [mockHostedTool] });
|
|
95
95
|
const serialized = (await toolkit.list()).map((tool) => tool.serialize());
|
|
96
96
|
expect(serialized).toHaveLength(1);
|
|
97
97
|
expect(serialized[0]).toEqual({
|
|
98
|
-
|
|
98
|
+
kind: "provider-defined",
|
|
99
99
|
id: mockHostedTool.id,
|
|
100
100
|
name: mockHostedTool.name,
|
|
101
|
-
|
|
101
|
+
args: mockHostedTool.providerData,
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
104
|
it("should serialize both function and hosted tools", async () => {
|
|
@@ -107,17 +107,20 @@ describe("FunctionToolkit", () => {
|
|
|
107
107
|
expect(serialized).toHaveLength(2);
|
|
108
108
|
// Check both tools are present (order not guaranteed with Map)
|
|
109
109
|
expect(serialized).toContainEqual({
|
|
110
|
-
|
|
110
|
+
kind: "provider-defined",
|
|
111
111
|
id: mockHostedTool.id,
|
|
112
112
|
name: mockHostedTool.name,
|
|
113
|
-
|
|
113
|
+
args: mockHostedTool.providerData,
|
|
114
114
|
});
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
// Check that function tool is present with correct structure
|
|
116
|
+
const functionTool = serialized.find((t) => t.kind === "function");
|
|
117
|
+
expect(functionTool).toBeDefined();
|
|
118
|
+
expect(functionTool).toMatchObject({
|
|
119
|
+
kind: "function",
|
|
120
|
+
name: simpleStringTool.id,
|
|
118
121
|
description: simpleStringTool.description,
|
|
119
|
-
parameters: simpleStringTool.parameters,
|
|
120
122
|
});
|
|
123
|
+
expect(functionTool.parameters).toBeDefined();
|
|
121
124
|
});
|
|
122
125
|
it("should handle hosted tools without providerData", async () => {
|
|
123
126
|
const toolkit = new FunctionToolkit({ id: "test", tools: [anotherHostedTool] });
|
package/dist/tool/tool.js
CHANGED
|
@@ -120,9 +120,9 @@ export class FunctionTool extends BaseTool {
|
|
|
120
120
|
kind: "function",
|
|
121
121
|
name: this.id,
|
|
122
122
|
description: this.description,
|
|
123
|
-
parameters: (this.parameters
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
parameters: z.toJSONSchema(this.parameters ?? z.object({}), {
|
|
124
|
+
target: "draft-7",
|
|
125
|
+
}), // Use empty object if no parameters (matches AI SDK)
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
}
|