@vincemakes/kiso-server 0.41.0 → 0.41.2
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 +30 -0
- package/dist/errors.d.ts +9 -0
- package/dist/errors.js +14 -0
- package/dist/http.js +5 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/service.d.ts +41 -6
- package/dist/service.js +40 -10
- package/dist/wire.d.ts +9 -4
- package/dist/wire.js +17 -6
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -50,6 +50,36 @@ await service.close(5_000); // abort what is live,
|
|
|
50
50
|
- **Product frames on the stream** (billing, estimates, couriers) are the
|
|
51
51
|
transport's business; the service delivers the durable events only.
|
|
52
52
|
|
|
53
|
+
## What the host owns
|
|
54
|
+
|
|
55
|
+
- **The store.** `createSessionService({ store })` takes the store the
|
|
56
|
+
factory's agents write to; the service never closes it — `close()`
|
|
57
|
+
aborts runs and forgets sessions, and the host calls `store.closeAll()`
|
|
58
|
+
when its process ends. Everything about an open session is read from
|
|
59
|
+
that session's own log (the object the run writes), so replay and run
|
|
60
|
+
share one truth by construction; a factory that binds its agents to a
|
|
61
|
+
DIFFERENT store is detected — the first run to settle on such a session
|
|
62
|
+
ends with `StoreMismatchError` and the session is refused from then on.
|
|
63
|
+
- **The sanitizer's guarantee.** The default `sanitizeToolArgs` strips
|
|
64
|
+
prose keys and truncates long strings at every depth. It is a UI-noise
|
|
65
|
+
and casual-leak filter for a tool card, not a privacy boundary: a
|
|
66
|
+
product that must guarantee no prose reaches a client supplies its own
|
|
67
|
+
`sanitize` in the projection options.
|
|
68
|
+
|
|
69
|
+
## Shutting down, and one thing not to call
|
|
70
|
+
|
|
71
|
+
The order that keeps every run resumable: `service.drain(graceMs)` (no
|
|
72
|
+
new runs; wait for executing tools; the report says what was parked or
|
|
73
|
+
cut), then stop the listener, then `store.closeAll()`, then exit. Do not
|
|
74
|
+
call `service.close()` for that — it ABORTS what is live, including a
|
|
75
|
+
run parked at an approval, which voids the draft its answer would have
|
|
76
|
+
continued. And do not call `agent.close()` on a factory's agent when the
|
|
77
|
+
store is shared: in the runtime it closes the whole store.
|
|
78
|
+
|
|
79
|
+
`hooks.onSettled` is the product's "what next" after a run: a follow-up
|
|
80
|
+
turn, a resume when `uncertainRemaining` reaches zero, a notification.
|
|
81
|
+
The service never starts a run on its own.
|
|
82
|
+
|
|
53
83
|
## What a host still writes
|
|
54
84
|
|
|
55
85
|
The transport (routes, SSE framing, `Last-Event-ID`, keepalive, status
|
package/dist/errors.d.ts
CHANGED
|
@@ -19,6 +19,15 @@ export declare class OpenRunError extends Error {
|
|
|
19
19
|
readonly runId: string;
|
|
20
20
|
constructor(sessionId: string, runId: string);
|
|
21
21
|
}
|
|
22
|
+
/** The factory's agent writes to a different store than the service was
|
|
23
|
+
* given: a run settled and its terminal is not on the service's store.
|
|
24
|
+
* Two stores are two truths, which kiso does not allow — the session is
|
|
25
|
+
* refused from here on (the host's wiring is wrong, not the run). */
|
|
26
|
+
export declare class StoreMismatchError extends Error {
|
|
27
|
+
readonly sessionId: string;
|
|
28
|
+
readonly runId: string;
|
|
29
|
+
constructor(sessionId: string, runId: string, expectedSeq: number);
|
|
30
|
+
}
|
|
22
31
|
/** drain() was called: no new run or resume starts in this process. */
|
|
23
32
|
export declare class DrainingError extends Error {
|
|
24
33
|
constructor();
|
package/dist/errors.js
CHANGED
|
@@ -29,6 +29,20 @@ export class OpenRunError extends Error {
|
|
|
29
29
|
this.runId = runId;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
/** The factory's agent writes to a different store than the service was
|
|
33
|
+
* given: a run settled and its terminal is not on the service's store.
|
|
34
|
+
* Two stores are two truths, which kiso does not allow — the session is
|
|
35
|
+
* refused from here on (the host's wiring is wrong, not the run). */
|
|
36
|
+
export class StoreMismatchError extends Error {
|
|
37
|
+
sessionId;
|
|
38
|
+
runId;
|
|
39
|
+
constructor(sessionId, runId, expectedSeq) {
|
|
40
|
+
super(`session ${sessionId}: run ${runId} settled at seq ${expectedSeq} but the service's store does not hold it — the agent factory's store is not the service's store`);
|
|
41
|
+
this.name = "StoreMismatchError";
|
|
42
|
+
this.sessionId = sessionId;
|
|
43
|
+
this.runId = runId;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
32
46
|
/** drain() was called: no new run or resume starts in this process. */
|
|
33
47
|
export class DrainingError extends Error {
|
|
34
48
|
constructor() {
|
package/dist/http.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { DrainingError, InFlightError, OpenRunError } from "./errors.js";
|
|
2
2
|
import { toWireEvent } from "./wire.js";
|
|
3
|
-
|
|
3
|
+
/** The store's own rule (`^[A-Za-z0-9._-]+$`), with a length cap. 0.41.2:
|
|
4
|
+
* the first version also required a leading letter or digit, which the
|
|
5
|
+
* store never did — a nanoid can start with `_` or `-` (about 1 in 32 do)
|
|
6
|
+
* and every such session answered 404. */
|
|
7
|
+
const SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
4
8
|
const ACTIONS = new Set(["", "state", "events", "replay", "run", "resume", "abort", "approve", "uncertain"]);
|
|
5
9
|
export function createHttpHandler(service, options) {
|
|
6
10
|
const prefix = (options.prefix ?? "/v1/sessions").replace(/\/+$/, "");
|
package/dist/index.d.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* the agent factory, and every run is the runtime's own `Session.run()`.
|
|
8
8
|
*/
|
|
9
9
|
export { createSessionService, SessionService } from "./service.js";
|
|
10
|
-
export type { AbortOutcome, DrainReport, OpenRun, RunHandle, RunOptions, SessionServiceOptions } from "./service.js";
|
|
11
|
-
export { DrainingError, InFlightError, OpenRunError } from "./errors.js";
|
|
10
|
+
export type { AbortOutcome, DrainReport, OpenRun, RunHandle, RunOptions, SessionServiceOptions, SettledRun } from "./service.js";
|
|
11
|
+
export { DrainingError, InFlightError, OpenRunError, StoreMismatchError } from "./errors.js";
|
|
12
12
|
export { EXECUTION_ENDED, executionDelta } from "./execution.js";
|
|
13
13
|
export { tail } from "./tail.js";
|
|
14
|
-
export { MAX_ARG_VALUE_CHARS, STRIPPED_ARG_KEYS, sanitizeToolArgs, toWireEvent } from "./wire.js";
|
|
14
|
+
export { MAX_ARG_DEPTH, MAX_ARG_VALUE_CHARS, STRIPPED_ARG_KEYS, sanitizeToolArgs, toWireEvent } from "./wire.js";
|
|
15
15
|
export type { ProjectionOptions } from "./wire.js";
|
|
16
16
|
export type { Listener } from "./tail.js";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* the agent factory, and every run is the runtime's own `Session.run()`.
|
|
8
8
|
*/
|
|
9
9
|
export { createSessionService, SessionService } from "./service.js";
|
|
10
|
-
export { DrainingError, InFlightError, OpenRunError } from "./errors.js";
|
|
10
|
+
export { DrainingError, InFlightError, OpenRunError, StoreMismatchError } from "./errors.js";
|
|
11
11
|
export { EXECUTION_ENDED, executionDelta } from "./execution.js";
|
|
12
12
|
export { tail } from "./tail.js";
|
|
13
|
-
export { MAX_ARG_VALUE_CHARS, STRIPPED_ARG_KEYS, sanitizeToolArgs, toWireEvent } from "./wire.js";
|
|
13
|
+
export { MAX_ARG_DEPTH, MAX_ARG_VALUE_CHARS, STRIPPED_ARG_KEYS, sanitizeToolArgs, toWireEvent } from "./wire.js";
|
package/dist/service.d.ts
CHANGED
|
@@ -20,9 +20,19 @@ import { type Listener } from "./tail.js";
|
|
|
20
20
|
* loop of its own.
|
|
21
21
|
*/
|
|
22
22
|
export interface SessionServiceOptions {
|
|
23
|
-
/** The store every session in this process lives on — the
|
|
24
|
-
* factory's agents write to. The service reads it for
|
|
25
|
-
*
|
|
23
|
+
/** The store every session in this process lives on — the SAME one the
|
|
24
|
+
* factory's agents write to. The service reads it for boot recovery
|
|
25
|
+
* (`openRuns()`) and for cold reads of sessions it has not opened;
|
|
26
|
+
* everything about an open session is read from that session's own
|
|
27
|
+
* log, so the run that writes and the replay that reads share one
|
|
28
|
+
* object by construction. The remaining way to get two stores — a
|
|
29
|
+
* factory that binds its agents to another store — is DETECTED: the
|
|
30
|
+
* first run to settle on such a session ends with StoreMismatchError
|
|
31
|
+
* and the session is refused from then on.
|
|
32
|
+
*
|
|
33
|
+
* Ownership: the host owns the store. `close()` aborts runs and
|
|
34
|
+
* forgets sessions; it never closes the store — the host calls
|
|
35
|
+
* `store.closeAll()` when its process ends. */
|
|
26
36
|
readonly store: SessionStore;
|
|
27
37
|
/** The product's agent factory, called once per session id (and again by
|
|
28
38
|
* `reopen`). Everything product-specific — prompt, tools, extensions,
|
|
@@ -38,8 +48,26 @@ export interface SessionServiceOptions {
|
|
|
38
48
|
/** After `abort()` has aborted a run: the product's cleanup, e.g.
|
|
39
49
|
* finishing paid jobs the run left in flight. */
|
|
40
50
|
readonly onAbort?: (sessionId: string, runId: string) => Promise<void> | void;
|
|
51
|
+
/** After a run has settled — its terminal delivered to every
|
|
52
|
+
* listener, the session idle again. The product's "what next": a
|
|
53
|
+
* follow-up turn (`service.run`), a resume when the last uncertain
|
|
54
|
+
* verdict is in (`uncertainRemaining === 0`), a notification. The
|
|
55
|
+
* service itself never starts a run on its own. Runs after the
|
|
56
|
+
* store check, so a mismatched session never reaches it. */
|
|
57
|
+
readonly onSettled?: (settled: SettledRun) => Promise<void> | void;
|
|
41
58
|
};
|
|
42
59
|
}
|
|
60
|
+
/** What `hooks.onSettled` receives. */
|
|
61
|
+
export interface SettledRun {
|
|
62
|
+
readonly sessionId: string;
|
|
63
|
+
readonly runId: string;
|
|
64
|
+
/** The terminal's outcome kind: completed | aborted | error | max_turns | max_tokens | hook_stopped. */
|
|
65
|
+
readonly outcome: string;
|
|
66
|
+
/** Executions still awaiting a human verdict after this run. */
|
|
67
|
+
readonly uncertainRemaining: number;
|
|
68
|
+
/** The highest seq this run delivered. */
|
|
69
|
+
readonly highWater: number;
|
|
70
|
+
}
|
|
43
71
|
export interface RunHandle {
|
|
44
72
|
readonly runId: string;
|
|
45
73
|
/** Settles when the run has reached its terminal and the last event has
|
|
@@ -121,9 +149,12 @@ export declare class SessionService {
|
|
|
121
149
|
readonly remaining: number;
|
|
122
150
|
}>;
|
|
123
151
|
/** Every event after `after` — the ones on disk now, then the live ones
|
|
124
|
-
* as they land — exactly once each, in order. Returns the unsubscribe.
|
|
152
|
+
* as they land — exactly once each, in order. Returns the unsubscribe.
|
|
153
|
+
* The replay reads the OPEN session's own log, never a second copy. */
|
|
125
154
|
subscribe(sessionId: string, after: number, listener: Listener): Promise<() => void>;
|
|
126
|
-
/** The durable log after `after
|
|
155
|
+
/** The durable log after `after`. An open session answers from its own
|
|
156
|
+
* log; a session this process has not opened is read from the store
|
|
157
|
+
* (a cold read, nothing opened). */
|
|
127
158
|
events(sessionId: string, after?: number): Event[];
|
|
128
159
|
isRunning(sessionId: string): boolean;
|
|
129
160
|
/** The highest seq this process has delivered for the session, −1 before any. */
|
|
@@ -133,6 +164,8 @@ export declare class SessionService {
|
|
|
133
164
|
/** The run a previous process left without a terminal on this session,
|
|
134
165
|
* or null. The plain read the transport's snapshot needs. */
|
|
135
166
|
openRun(sessionId: string): string | null;
|
|
167
|
+
/** True once a settled run proved the factory's store is not this one. */
|
|
168
|
+
mismatched(sessionId: string): boolean;
|
|
136
169
|
/** Boot recovery: every session on the store whose last run has no
|
|
137
170
|
* terminal. The host decides what to do with them (resume, or leave
|
|
138
171
|
* for the next request to find). */
|
|
@@ -144,7 +177,9 @@ export declare class SessionService {
|
|
|
144
177
|
/** Abort every live run, wait up to `graceMs` for each to settle (a
|
|
145
178
|
* tool that ignores the abort keeps its run open until it returns),
|
|
146
179
|
* then forget every session. Whatever did not settle is resumable by
|
|
147
|
-
* the next process — persist-first means nothing delivered is lost.
|
|
180
|
+
* the next process — persist-first means nothing delivered is lost.
|
|
181
|
+
* The STORE is not closed here: the host owns it and calls
|
|
182
|
+
* `store.closeAll()` when its process ends. */
|
|
148
183
|
close(graceMs?: number): Promise<void>;
|
|
149
184
|
}
|
|
150
185
|
export declare function createSessionService(options: SessionServiceOptions): SessionService;
|
package/dist/service.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { openRunId } from "@vincemakes/kiso-runtime/internal";
|
|
2
|
-
import { DrainingError, InFlightError, OpenRunError } from "./errors.js";
|
|
2
|
+
import { DrainingError, InFlightError, OpenRunError, StoreMismatchError } from "./errors.js";
|
|
3
3
|
import { executionDelta } from "./execution.js";
|
|
4
4
|
import { tail } from "./tail.js";
|
|
5
5
|
export class SessionService {
|
|
@@ -24,7 +24,7 @@ export class SessionService {
|
|
|
24
24
|
const opening = (async () => {
|
|
25
25
|
const agent = await this.#open(sessionId);
|
|
26
26
|
const session = await agent.session({ id: sessionId });
|
|
27
|
-
const held = { id: sessionId, agent, session, listeners: new Set(), live: null, pumping: null, executing: 0, highWater: -1 };
|
|
27
|
+
const held = { id: sessionId, agent, session, listeners: new Set(), live: null, pumping: null, executing: 0, highWater: -1, mismatch: null };
|
|
28
28
|
this.#held.set(sessionId, held);
|
|
29
29
|
return held;
|
|
30
30
|
})();
|
|
@@ -73,6 +73,24 @@ export class SessionService {
|
|
|
73
73
|
held.executing = 0;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
// the one-truth check: what this run wrote must be on the store the
|
|
77
|
+
// service was given. Persist-first means the terminal is on disk
|
|
78
|
+
// before it was yielded above; a store that does not hold it is a
|
|
79
|
+
// different store.
|
|
80
|
+
if (held.highWater >= 0 && !this.#store.load(held.id).some((r) => r.event.seq === held.highWater)) {
|
|
81
|
+
held.mismatch = new StoreMismatchError(held.id, run.runId, held.highWater);
|
|
82
|
+
throw held.mismatch;
|
|
83
|
+
}
|
|
84
|
+
if (this.#hooks.onSettled !== undefined) {
|
|
85
|
+
const terminal = [...held.session.log.all].reverse().find((e) => e.type === "terminal");
|
|
86
|
+
await this.#hooks.onSettled({
|
|
87
|
+
sessionId: held.id,
|
|
88
|
+
runId: run.runId,
|
|
89
|
+
outcome: terminal !== undefined && terminal.type === "terminal" ? terminal.outcome.kind : "unknown",
|
|
90
|
+
uncertainRemaining: held.session.uncertainExecutions().length,
|
|
91
|
+
highWater: held.highWater,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
76
94
|
})();
|
|
77
95
|
held.pumping = pumping;
|
|
78
96
|
void pumping.catch(() => { }); // a host that does not await `done` gets no unhandled rejection
|
|
@@ -85,6 +103,8 @@ export class SessionService {
|
|
|
85
103
|
if (this.#draining)
|
|
86
104
|
throw new DrainingError();
|
|
87
105
|
const held = await this.#hold(sessionId);
|
|
106
|
+
if (held.mismatch !== null)
|
|
107
|
+
throw held.mismatch;
|
|
88
108
|
if (held.live !== null)
|
|
89
109
|
throw new InFlightError(sessionId, held.live.runId);
|
|
90
110
|
const open = openRunId(this.#store.load(sessionId));
|
|
@@ -102,6 +122,8 @@ export class SessionService {
|
|
|
102
122
|
if (this.#draining)
|
|
103
123
|
throw new DrainingError();
|
|
104
124
|
const held = await this.#hold(sessionId);
|
|
125
|
+
if (held.mismatch !== null)
|
|
126
|
+
throw held.mismatch;
|
|
105
127
|
if (held.live !== null)
|
|
106
128
|
throw new InFlightError(sessionId, held.live.runId);
|
|
107
129
|
return this.#pump(held, held.session.resume());
|
|
@@ -155,17 +177,19 @@ export class SessionService {
|
|
|
155
177
|
}
|
|
156
178
|
// ---- observation ----------------------------------------------------------
|
|
157
179
|
/** Every event after `after` — the ones on disk now, then the live ones
|
|
158
|
-
* as they land — exactly once each, in order. Returns the unsubscribe.
|
|
180
|
+
* as they land — exactly once each, in order. Returns the unsubscribe.
|
|
181
|
+
* The replay reads the OPEN session's own log, never a second copy. */
|
|
159
182
|
async subscribe(sessionId, after, listener) {
|
|
160
183
|
const held = await this.#hold(sessionId);
|
|
161
|
-
return tail(() =>
|
|
184
|
+
return tail(() => held.session.log.all.filter((e) => e.seq > after), held.listeners, after, listener);
|
|
162
185
|
}
|
|
163
|
-
/** The durable log after `after
|
|
186
|
+
/** The durable log after `after`. An open session answers from its own
|
|
187
|
+
* log; a session this process has not opened is read from the store
|
|
188
|
+
* (a cold read, nothing opened). */
|
|
164
189
|
events(sessionId, after = -1) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
.filter((e) => e.seq > after);
|
|
190
|
+
const held = this.#held.get(sessionId);
|
|
191
|
+
const events = held !== undefined ? held.session.log.all : this.#store.load(sessionId).map((r) => r.event);
|
|
192
|
+
return events.filter((e) => e.seq > after);
|
|
169
193
|
}
|
|
170
194
|
isRunning(sessionId) {
|
|
171
195
|
return this.#held.get(sessionId)?.live !== null && this.#held.get(sessionId)?.live !== undefined;
|
|
@@ -188,6 +212,10 @@ export class SessionService {
|
|
|
188
212
|
openRun(sessionId) {
|
|
189
213
|
return openRunId(this.#store.load(sessionId)) ?? null;
|
|
190
214
|
}
|
|
215
|
+
/** True once a settled run proved the factory's store is not this one. */
|
|
216
|
+
mismatched(sessionId) {
|
|
217
|
+
return this.#held.get(sessionId)?.mismatch !== null && this.#held.get(sessionId)?.mismatch !== undefined;
|
|
218
|
+
}
|
|
191
219
|
/** Boot recovery: every session on the store whose last run has no
|
|
192
220
|
* terminal. The host decides what to do with them (resume, or leave
|
|
193
221
|
* for the next request to find). */
|
|
@@ -237,7 +265,9 @@ export class SessionService {
|
|
|
237
265
|
/** Abort every live run, wait up to `graceMs` for each to settle (a
|
|
238
266
|
* tool that ignores the abort keeps its run open until it returns),
|
|
239
267
|
* then forget every session. Whatever did not settle is resumable by
|
|
240
|
-
* the next process — persist-first means nothing delivered is lost.
|
|
268
|
+
* the next process — persist-first means nothing delivered is lost.
|
|
269
|
+
* The STORE is not closed here: the host owns it and calls
|
|
270
|
+
* `store.closeAll()` when its process ends. */
|
|
241
271
|
async close(graceMs = 0) {
|
|
242
272
|
this.#draining = true;
|
|
243
273
|
const settling = [];
|
package/dist/wire.d.ts
CHANGED
|
@@ -22,8 +22,13 @@ export interface ProjectionOptions {
|
|
|
22
22
|
export declare const STRIPPED_ARG_KEYS: ReadonlySet<string>;
|
|
23
23
|
/** Longer string values are cut here; the card shows a prefix. */
|
|
24
24
|
export declare const MAX_ARG_VALUE_CHARS = 200;
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
/** Nesting beyond this is replaced by a marker rather than walked. */
|
|
26
|
+
export declare const MAX_ARG_DEPTH = 8;
|
|
27
|
+
/** The default sanitizer: drops the prose keys and truncates long strings
|
|
28
|
+
* AT EVERY DEPTH — objects and arrays are walked (0.41.1; the 0.41.0
|
|
29
|
+
* version was shallow, so `{ options: { prompt } }` passed the prompt
|
|
30
|
+
* through). What it is: a UI-noise and casual-leak filter for a tool
|
|
31
|
+
* card. What it is not: a privacy boundary — a product that must
|
|
32
|
+
* guarantee no prose reaches a client supplies its own `sanitize`. */
|
|
33
|
+
export declare function sanitizeToolArgs(input: unknown, depth?: number): unknown;
|
|
29
34
|
export declare function toWireEvent(event: Event, options?: ProjectionOptions): WireEvent | null;
|
package/dist/wire.js
CHANGED
|
@@ -5,17 +5,28 @@ import { DURABLE_TO_WIRE, SANITIZED_FIELDS, WIRE_FIELDS } from "@vincemakes/kiso
|
|
|
5
5
|
export const STRIPPED_ARG_KEYS = new Set(["prompt", "description", "text", "body", "content", "instructions", "system_prompt", "input_text"]);
|
|
6
6
|
/** Longer string values are cut here; the card shows a prefix. */
|
|
7
7
|
export const MAX_ARG_VALUE_CHARS = 200;
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/** Nesting beyond this is replaced by a marker rather than walked. */
|
|
9
|
+
export const MAX_ARG_DEPTH = 8;
|
|
10
|
+
/** The default sanitizer: drops the prose keys and truncates long strings
|
|
11
|
+
* AT EVERY DEPTH — objects and arrays are walked (0.41.1; the 0.41.0
|
|
12
|
+
* version was shallow, so `{ options: { prompt } }` passed the prompt
|
|
13
|
+
* through). What it is: a UI-noise and casual-leak filter for a tool
|
|
14
|
+
* card. What it is not: a privacy boundary — a product that must
|
|
15
|
+
* guarantee no prose reaches a client supplies its own `sanitize`. */
|
|
16
|
+
export function sanitizeToolArgs(input, depth = 0) {
|
|
17
|
+
if (typeof input === "string")
|
|
18
|
+
return input.length > MAX_ARG_VALUE_CHARS ? `${input.slice(0, MAX_ARG_VALUE_CHARS)}…` : input;
|
|
19
|
+
if (input === null || typeof input !== "object")
|
|
13
20
|
return input;
|
|
21
|
+
if (depth >= MAX_ARG_DEPTH)
|
|
22
|
+
return Array.isArray(input) ? "[…]" : "{…}";
|
|
23
|
+
if (Array.isArray(input))
|
|
24
|
+
return input.map((v) => sanitizeToolArgs(v, depth + 1));
|
|
14
25
|
const out = {};
|
|
15
26
|
for (const [key, value] of Object.entries(input)) {
|
|
16
27
|
if (STRIPPED_ARG_KEYS.has(key))
|
|
17
28
|
continue;
|
|
18
|
-
out[key] =
|
|
29
|
+
out[key] = sanitizeToolArgs(value, depth + 1);
|
|
19
30
|
}
|
|
20
31
|
return out;
|
|
21
32
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-server",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.2",
|
|
4
4
|
"description": "kiso server — the hosted-session service: many durable sessions in one process, observers with exact replay from a sequence number, approve / abort / resume routed to the right run, drain and close. No transport; a product supplies the agent factory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"test": "vitest run"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@vincemakes/kiso-core": "0.41.
|
|
29
|
-
"@vincemakes/kiso-protocol": "0.41.
|
|
30
|
-
"@vincemakes/kiso-runtime": "0.41.
|
|
28
|
+
"@vincemakes/kiso-core": "0.41.2",
|
|
29
|
+
"@vincemakes/kiso-protocol": "0.41.2",
|
|
30
|
+
"@vincemakes/kiso-runtime": "0.41.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@vincemakes/kiso-evals": "0.41.
|
|
33
|
+
"@vincemakes/kiso-evals": "0.41.2",
|
|
34
34
|
"@types/node": "^26.1.2",
|
|
35
35
|
"typescript": "^5.7.2",
|
|
36
36
|
"vitest": "^3.0.0"
|