@vendoai/agent 0.4.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/LICENSE +202 -0
- package/README.md +26 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +443 -0
- package/dist/agent.js.map +1 -0
- package/dist/capability-miss.d.ts +24 -0
- package/dist/capability-miss.d.ts.map +1 -0
- package/dist/capability-miss.js +133 -0
- package/dist/capability-miss.js.map +1 -0
- package/dist/ids.d.ts +6 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +9 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/pack.d.ts +37 -0
- package/dist/pack.d.ts.map +1 -0
- package/dist/pack.js +225 -0
- package/dist/pack.js.map +1 -0
- package/dist/prompt.d.ts +8 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +43 -0
- package/dist/prompt.js.map +1 -0
- package/dist/runner.d.ts +20 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +114 -0
- package/dist/runner.js.map +1 -0
- package/dist/test-helpers.d.ts +55 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +230 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/threads.d.ts +48 -0
- package/dist/threads.d.ts.map +1 -0
- package/dist/threads.js +251 -0
- package/dist/threads.js.map +1 -0
- package/dist/tool-pack.d.ts +41 -0
- package/dist/tool-pack.d.ts.map +1 -0
- package/dist/tool-pack.js +17 -0
- package/dist/tool-pack.js.map +1 -0
- package/dist/tool-search.d.ts +72 -0
- package/dist/tool-search.d.ts.map +1 -0
- package/dist/tool-search.js +135 -0
- package/dist/tool-search.js.map +1 -0
- package/dist/tools.d.ts +22 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +151 -0
- package/dist/tools.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { MockLanguageModelV3, simulateReadableStream } from "ai/test";
|
|
2
|
+
import { expect } from "vitest";
|
|
3
|
+
export const ZERO_USAGE = {
|
|
4
|
+
inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
|
|
5
|
+
outputTokens: { total: 0, text: 0, reasoning: 0 },
|
|
6
|
+
};
|
|
7
|
+
export function textTurn(text, id = "text_1") {
|
|
8
|
+
return [
|
|
9
|
+
{ type: "text-start", id },
|
|
10
|
+
{ type: "text-delta", id, delta: text },
|
|
11
|
+
{ type: "text-end", id },
|
|
12
|
+
{ type: "finish", usage: ZERO_USAGE, finishReason: { unified: "stop", raw: undefined } },
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
export function toolCallTurn(toolName, input, toolCallId = "call_1") {
|
|
16
|
+
return [
|
|
17
|
+
{ type: "tool-call", toolCallId, toolName, input: JSON.stringify(input) },
|
|
18
|
+
{ type: "finish", usage: ZERO_USAGE, finishReason: { unified: "tool-calls", raw: undefined } },
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
export function scriptedModel(turns) {
|
|
22
|
+
const remaining = turns.map((turn) => [...turn]);
|
|
23
|
+
const prompts = [];
|
|
24
|
+
const toolNamesPerCall = [];
|
|
25
|
+
const shift = (request) => {
|
|
26
|
+
prompts.push(structuredClone(request.prompt));
|
|
27
|
+
toolNamesPerCall.push((request.tools ?? []).map((tool) => tool.name));
|
|
28
|
+
const chunks = remaining.shift();
|
|
29
|
+
if (chunks === undefined)
|
|
30
|
+
throw new Error("scripted model exhausted");
|
|
31
|
+
return chunks;
|
|
32
|
+
};
|
|
33
|
+
const model = new MockLanguageModelV3({
|
|
34
|
+
doStream: async (request) => {
|
|
35
|
+
const chunks = shift(request);
|
|
36
|
+
return { stream: simulateReadableStream({ chunks }) };
|
|
37
|
+
},
|
|
38
|
+
doGenerate: async (request) => {
|
|
39
|
+
const chunks = shift(request);
|
|
40
|
+
const finish = chunks.find((part) => part.type === "finish");
|
|
41
|
+
const content = [];
|
|
42
|
+
const text = chunks
|
|
43
|
+
.filter((part) => part.type === "text-delta")
|
|
44
|
+
.map((part) => part.delta)
|
|
45
|
+
.join("");
|
|
46
|
+
if (text.length > 0)
|
|
47
|
+
content.push({ type: "text", text });
|
|
48
|
+
for (const part of chunks) {
|
|
49
|
+
if (part.type === "tool-call")
|
|
50
|
+
content.push(structuredClone(part));
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
content,
|
|
54
|
+
finishReason: finish?.finishReason ?? { unified: "stop", raw: undefined },
|
|
55
|
+
usage: finish?.usage ?? ZERO_USAGE,
|
|
56
|
+
warnings: [],
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
model.prompts = prompts;
|
|
61
|
+
model.toolNamesPerCall = toolNamesPerCall;
|
|
62
|
+
return model;
|
|
63
|
+
}
|
|
64
|
+
function deepFreeze(value) {
|
|
65
|
+
if (value !== null && typeof value === "object") {
|
|
66
|
+
for (const child of Object.values(value))
|
|
67
|
+
deepFreeze(child);
|
|
68
|
+
Object.freeze(value);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
export function testGuard(policy, directions = []) {
|
|
73
|
+
const approvalsByCall = new Map();
|
|
74
|
+
const decisions = new Map();
|
|
75
|
+
const subscribers = new Set();
|
|
76
|
+
const events = [];
|
|
77
|
+
const directionValues = [...directions];
|
|
78
|
+
const guard = {
|
|
79
|
+
events,
|
|
80
|
+
directionValues,
|
|
81
|
+
abandoned: [],
|
|
82
|
+
// AGENT-6: mirror the real guard — abandoning denies each still-pending
|
|
83
|
+
// approval (idempotent; unknown/decided ids are no-ops) and notifies
|
|
84
|
+
// decision subscribers.
|
|
85
|
+
async abandonApprovals(ids) {
|
|
86
|
+
for (const id of ids) {
|
|
87
|
+
const known = [...approvalsByCall.values()].some((approval) => approval.id === id);
|
|
88
|
+
if (!known || decisions.has(id))
|
|
89
|
+
continue;
|
|
90
|
+
guard.abandoned.push(id);
|
|
91
|
+
guard.decide(id, false);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
async check(call, descriptor, runCtx) {
|
|
95
|
+
const action = policy[call.tool] ?? "run";
|
|
96
|
+
if (action === "run")
|
|
97
|
+
return { action: "run", decidedBy: "default" };
|
|
98
|
+
if (action === "block")
|
|
99
|
+
return { action: "block", reason: "blocked", decidedBy: "rule" };
|
|
100
|
+
let approval = approvalsByCall.get(call.id);
|
|
101
|
+
if (approval === undefined) {
|
|
102
|
+
approval = {
|
|
103
|
+
id: `apr_${call.id}`,
|
|
104
|
+
call: structuredClone(call),
|
|
105
|
+
descriptor: deepFreeze(structuredClone(descriptor)),
|
|
106
|
+
inputPreview: JSON.stringify(call.args),
|
|
107
|
+
ctx: {
|
|
108
|
+
principal: structuredClone(runCtx.principal),
|
|
109
|
+
venue: runCtx.venue,
|
|
110
|
+
presence: runCtx.presence,
|
|
111
|
+
...(runCtx.appId === undefined ? {} : { appId: runCtx.appId }),
|
|
112
|
+
...(runCtx.trigger === undefined ? {} : { trigger: structuredClone(runCtx.trigger) }),
|
|
113
|
+
},
|
|
114
|
+
createdAt: new Date().toISOString(),
|
|
115
|
+
};
|
|
116
|
+
approvalsByCall.set(call.id, approval);
|
|
117
|
+
}
|
|
118
|
+
const approved = decisions.get(approval.id);
|
|
119
|
+
if (approved === true)
|
|
120
|
+
return { action: "run", decidedBy: "default" };
|
|
121
|
+
if (approved === false)
|
|
122
|
+
return { action: "block", reason: "denied", decidedBy: "rule" };
|
|
123
|
+
return { action: "ask", approval, decidedBy: "rule" };
|
|
124
|
+
},
|
|
125
|
+
async report(event) {
|
|
126
|
+
events.push(structuredClone(event));
|
|
127
|
+
},
|
|
128
|
+
async directions() {
|
|
129
|
+
return [...directionValues];
|
|
130
|
+
},
|
|
131
|
+
onApprovalDecision(callback) {
|
|
132
|
+
subscribers.add(callback);
|
|
133
|
+
return () => subscribers.delete(callback);
|
|
134
|
+
},
|
|
135
|
+
decide(approvalId, approved) {
|
|
136
|
+
decisions.set(approvalId, approved);
|
|
137
|
+
for (const subscriber of subscribers)
|
|
138
|
+
subscriber(approvalId, approved);
|
|
139
|
+
},
|
|
140
|
+
pending() {
|
|
141
|
+
return [...approvalsByCall.values()].filter((approval) => !decisions.has(approval.id));
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
return guard;
|
|
145
|
+
}
|
|
146
|
+
export function boundRegistry(implementations, guard) {
|
|
147
|
+
const invocations = Object.fromEntries(Object.keys(implementations).map((name) => [name, 0]));
|
|
148
|
+
return {
|
|
149
|
+
invocations,
|
|
150
|
+
async descriptors() {
|
|
151
|
+
return Object.values(implementations).map(({ descriptor }) => structuredClone(descriptor));
|
|
152
|
+
},
|
|
153
|
+
async execute(call, runCtx) {
|
|
154
|
+
const implementation = implementations[call.tool];
|
|
155
|
+
if (implementation === undefined) {
|
|
156
|
+
return { status: "error", error: { code: "not-found", message: `Unknown tool: ${call.tool}` } };
|
|
157
|
+
}
|
|
158
|
+
const decision = await guard.check(call, implementation.descriptor, runCtx);
|
|
159
|
+
let outcome;
|
|
160
|
+
if (decision.action === "block") {
|
|
161
|
+
outcome = { status: "blocked", reason: decision.reason };
|
|
162
|
+
}
|
|
163
|
+
else if (decision.action === "ask") {
|
|
164
|
+
outcome = { status: "pending-approval", approvalId: decision.approval.id };
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
invocations[call.tool] = (invocations[call.tool] ?? 0) + 1;
|
|
168
|
+
try {
|
|
169
|
+
outcome = { status: "ok", output: await implementation.execute(call.args, runCtx, call) };
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
outcome = {
|
|
173
|
+
status: "error",
|
|
174
|
+
error: {
|
|
175
|
+
code: "execution",
|
|
176
|
+
message: error instanceof Error ? error.message : String(error),
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
await guard.report({
|
|
182
|
+
id: `aud_${call.id}`,
|
|
183
|
+
at: new Date().toISOString(),
|
|
184
|
+
kind: "tool-call",
|
|
185
|
+
principal: structuredClone(runCtx.principal),
|
|
186
|
+
venue: runCtx.venue,
|
|
187
|
+
presence: runCtx.presence,
|
|
188
|
+
...(runCtx.appId === undefined ? {} : { appId: runCtx.appId }),
|
|
189
|
+
...(runCtx.trigger === undefined ? {} : { trigger: structuredClone(runCtx.trigger) }),
|
|
190
|
+
tool: call.tool,
|
|
191
|
+
inputPreview: JSON.stringify(call.args),
|
|
192
|
+
outcome: outcome.status,
|
|
193
|
+
decidedBy: decision.decidedBy,
|
|
194
|
+
});
|
|
195
|
+
return outcome;
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// The core conformance kit ships the reference in-memory StoreAdapter; tests
|
|
200
|
+
// exercise the same double every other block will use.
|
|
201
|
+
export { memoryStoreAdapter as memoryStore } from "@vendoai/core/conformance";
|
|
202
|
+
export async function readSse(response) {
|
|
203
|
+
const raw = await response.text();
|
|
204
|
+
expect(raw.endsWith("\n\n")).toBe(true);
|
|
205
|
+
const blocks = raw.slice(0, -2).split("\n\n");
|
|
206
|
+
expect(blocks.length).toBeGreaterThan(0);
|
|
207
|
+
expect(blocks.every((block) => block.startsWith("data: ") && !block.includes("\n"))).toBe(true);
|
|
208
|
+
const rawFrames = blocks.map((block) => `${block}\n\n`);
|
|
209
|
+
expect(rawFrames.at(-1)).toBe("data: [DONE]\n\n");
|
|
210
|
+
const parts = blocks.slice(0, -1).map((block) => JSON.parse(block.slice("data: ".length)));
|
|
211
|
+
return { rawFrames, parts };
|
|
212
|
+
}
|
|
213
|
+
export function ctx(overrides = {}) {
|
|
214
|
+
return {
|
|
215
|
+
principal: { kind: "user", subject: "u1" },
|
|
216
|
+
venue: "chat",
|
|
217
|
+
presence: "present",
|
|
218
|
+
sessionId: "s1",
|
|
219
|
+
...overrides,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/** A minimal single-text-part user UIMessage — the shape every suite feeds stream(). */
|
|
223
|
+
export function userMessage(id, text) {
|
|
224
|
+
return { id, role: "user", parts: [{ type: "text", text }] };
|
|
225
|
+
}
|
|
226
|
+
/** The first assembled UIMessage part of a given type (e.g. "data-vendo-view"). */
|
|
227
|
+
export function partOfType(message, type) {
|
|
228
|
+
return message.parts.find((part) => part.type === type);
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=test-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-helpers.js","sourceRoot":"","sources":["../src/test-helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type IsoDateTime, type RunContext, type StoreAdapter, type ThreadId } from "@vendoai/core";
|
|
2
|
+
import type { UIMessage } from "ai";
|
|
3
|
+
/** 03-agent §5 */
|
|
4
|
+
export interface Thread {
|
|
5
|
+
id: ThreadId;
|
|
6
|
+
subject: string;
|
|
7
|
+
messages: UIMessage[];
|
|
8
|
+
/** Precomputed listing title. Persisted beside the thread so `list` need not load the
|
|
9
|
+
* full messages array to derive it; absent on legacy rows (derived from messages then). */
|
|
10
|
+
title?: string;
|
|
11
|
+
createdAt: IsoDateTime;
|
|
12
|
+
updatedAt: IsoDateTime;
|
|
13
|
+
}
|
|
14
|
+
/** 03-agent §5 */
|
|
15
|
+
export interface ThreadSummary {
|
|
16
|
+
id: ThreadId;
|
|
17
|
+
title: string;
|
|
18
|
+
updatedAt: IsoDateTime;
|
|
19
|
+
}
|
|
20
|
+
/** 03-agent §5 */
|
|
21
|
+
export declare class ThreadRepository {
|
|
22
|
+
private readonly store;
|
|
23
|
+
constructor(store: StoreAdapter);
|
|
24
|
+
resolve(id: ThreadId | undefined, ctx: RunContext): Promise<Thread>;
|
|
25
|
+
get(id: ThreadId, ctx: RunContext): Promise<Thread | null>;
|
|
26
|
+
list(ctx: RunContext): Promise<ThreadSummary[]>;
|
|
27
|
+
delete(id: ThreadId, ctx: RunContext): Promise<void>;
|
|
28
|
+
persist(thread: Thread, messages: UIMessage[]): Promise<void>;
|
|
29
|
+
/** The Thread as it should be written for this turn: merged messages, a
|
|
30
|
+
* freshly-derived listing title (never a stale prior title — `list` reads it
|
|
31
|
+
* back without loading messages), and a new updatedAt. */
|
|
32
|
+
private updatedThread;
|
|
33
|
+
private create;
|
|
34
|
+
/** AGENT-11 / ENG-237: drop a subject's threads from the store on session
|
|
35
|
+
* eviction. Store-backed: a no-op — the store's own TTL sweep already erased
|
|
36
|
+
* the rows (02-store §4 erase cascade) before the umbrella calls this, so the
|
|
37
|
+
* list below simply finds none. Internal-default (no `store` configured):
|
|
38
|
+
* the ONLY place those rows get reclaimed, since nothing else sweeps them.
|
|
39
|
+
* Returns the evicted thread ids so callers can release any per-thread state
|
|
40
|
+
* keyed by id (ENG-252 loadouts) — otherwise a reused `thr_*` id would inherit
|
|
41
|
+
* the evicted thread's searched-in tools. */
|
|
42
|
+
evictSubject(subject: string): Promise<ThreadId[]>;
|
|
43
|
+
/** Follows the store's pagination cursor to exhaustion (it pages at 100) —
|
|
44
|
+
* otherwise a subject's >100th thread, possibly their most recently active,
|
|
45
|
+
* would silently vanish from `list`/`evictSubject`. */
|
|
46
|
+
private listRecords;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=threads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threads.d.ts","sourceRoot":"","sources":["../src/threads.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAClI,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAMpC,kBAAkB;AAClB,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,QAAQ,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB;gGAC4F;IAC5F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,WAAW,CAAC;IACvB,SAAS,EAAE,WAAW,CAAC;CACxB;AAED,kBAAkB;AAClB,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,WAAW,CAAC;CACxB;AAqFD,kBAAkB;AAClB,qBAAa,gBAAgB;IAOf,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,YAAY;IAE1C,OAAO,CAAC,EAAE,EAAE,QAAQ,GAAG,SAAS,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBnE,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAY1D,IAAI,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAU/C,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpD,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA8CnE;;+DAE2D;IAC3D,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,MAAM;IAWd;;;;;;;kDAO8C;IACxC,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAOxD;;4DAEwD;YAC1C,WAAW;CAa1B"}
|
package/dist/threads.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { VendoError } from "@vendoai/core";
|
|
2
|
+
import { mintThreadId } from "./ids.js";
|
|
3
|
+
const THREAD_COLLECTION = "vendo_threads";
|
|
4
|
+
const THREAD_ID_PATTERN = /^thr_.+$/;
|
|
5
|
+
/** Reconstruct a Thread from a store record. The store seam (core §12) carries
|
|
6
|
+
* the id + timestamps on the VendoRecord envelope and the reserved
|
|
7
|
+
* vendo_threads routing projects `data` down to `{ subject, messages }` (02
|
|
8
|
+
* §2) — so the whole Thread is never inside `data`. Read it back from the
|
|
9
|
+
* envelope, not from `data`. */
|
|
10
|
+
function threadFromRecord(record) {
|
|
11
|
+
if (!THREAD_ID_PATTERN.test(record.id))
|
|
12
|
+
return null;
|
|
13
|
+
// Timestamps ride the envelope, but a hand-written row (threadStore(store).put
|
|
14
|
+
// accepts any Json) could still be malformed — validate before trusting them so
|
|
15
|
+
// one bad row cannot brick the whole listing.
|
|
16
|
+
if (typeof record.createdAt !== "string" || typeof record.updatedAt !== "string")
|
|
17
|
+
return null;
|
|
18
|
+
const data = record.data;
|
|
19
|
+
if (typeof data !== "object" || data === null)
|
|
20
|
+
return null;
|
|
21
|
+
const candidate = data;
|
|
22
|
+
if (typeof candidate.subject !== "string" || !Array.isArray(candidate.messages))
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
id: record.id,
|
|
26
|
+
subject: candidate.subject,
|
|
27
|
+
messages: candidate.messages,
|
|
28
|
+
...(typeof candidate.title === "string" ? { title: candidate.title } : {}),
|
|
29
|
+
createdAt: record.createdAt,
|
|
30
|
+
updatedAt: record.updatedAt,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function titleFor(thread) {
|
|
34
|
+
// A persisted title short-circuits the message scan (and the listing skips loading the
|
|
35
|
+
// messages array entirely once a title exists — routing.ts).
|
|
36
|
+
if (typeof thread.title === "string" && thread.title.trim())
|
|
37
|
+
return thread.title.slice(0, 80);
|
|
38
|
+
return deriveTitle(thread.messages);
|
|
39
|
+
}
|
|
40
|
+
function deriveTitle(messages) {
|
|
41
|
+
// Messages come from a Json array (parseThreadData accepts any shape), so a
|
|
42
|
+
// message may lack an iterable `parts` or carry non-text parts — tolerate both,
|
|
43
|
+
// skipping rather than throwing.
|
|
44
|
+
for (const message of messages) {
|
|
45
|
+
if (message === null || typeof message !== "object")
|
|
46
|
+
continue;
|
|
47
|
+
if (message.role !== "user")
|
|
48
|
+
continue;
|
|
49
|
+
const parts = message.parts;
|
|
50
|
+
if (!Array.isArray(parts))
|
|
51
|
+
continue;
|
|
52
|
+
for (const part of parts) {
|
|
53
|
+
if (part !== null && typeof part === "object"
|
|
54
|
+
&& part.type === "text"
|
|
55
|
+
&& typeof part.text === "string") {
|
|
56
|
+
const title = part.text.trim();
|
|
57
|
+
return title ? title.slice(0, 80) : "New thread";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return "New thread";
|
|
62
|
+
}
|
|
63
|
+
function toSummary(thread) {
|
|
64
|
+
return { id: thread.id, title: titleFor(thread), updatedAt: thread.updatedAt };
|
|
65
|
+
}
|
|
66
|
+
/** Serialize to plain JSON so the value satisfies the store seam's `Json` type
|
|
67
|
+
* (drops explicit `undefined`-valued props that JSON.stringify would omit). */
|
|
68
|
+
function toPlainJson(messages) {
|
|
69
|
+
return JSON.parse(JSON.stringify(messages));
|
|
70
|
+
}
|
|
71
|
+
// ENG-310 / kill-list B5: how many times persist re-reads and re-merges after
|
|
72
|
+
// losing a CAS race. Each retry starts from the freshly-read row, so a loss
|
|
73
|
+
// only recurs while OTHER writers keep landing between our read and our
|
|
74
|
+
// guarded write.
|
|
75
|
+
const MAX_PERSIST_ATTEMPTS = 5;
|
|
76
|
+
/** ENG-310: fold this turn's messages into the CURRENT persisted history —
|
|
77
|
+
* upsert by message id (same identity rule as the in-stream upsertMessage),
|
|
78
|
+
* so two concurrent turns on one thread both survive: shared history is
|
|
79
|
+
* updated in place, each turn's new messages are appended. */
|
|
80
|
+
function mergeMessages(current, turn) {
|
|
81
|
+
const merged = [...current];
|
|
82
|
+
for (const message of turn) {
|
|
83
|
+
const index = merged.findIndex((candidate) => candidate.id === message.id);
|
|
84
|
+
if (index === -1)
|
|
85
|
+
merged.push(message);
|
|
86
|
+
else
|
|
87
|
+
merged[index] = message;
|
|
88
|
+
}
|
|
89
|
+
return merged;
|
|
90
|
+
}
|
|
91
|
+
/** 03-agent §5 */
|
|
92
|
+
export class ThreadRepository {
|
|
93
|
+
store;
|
|
94
|
+
// kill-list B5: threads live ONLY in the store — a caller that omits
|
|
95
|
+
// `createAgent({ store })` still gets one; `createAgent` (agent.ts) passes
|
|
96
|
+
// core's in-memory `memoryStoreAdapter` (@vendoai/core/conformance) as that
|
|
97
|
+
// default, so every composition — BYO or store-backed — resolves, lists,
|
|
98
|
+
// persists, and evicts threads through this SAME code path. There is no
|
|
99
|
+
// separate in-memory branch here to keep behavior-parity with.
|
|
100
|
+
constructor(store) {
|
|
101
|
+
this.store = store;
|
|
102
|
+
}
|
|
103
|
+
async resolve(id, ctx) {
|
|
104
|
+
if (id === undefined)
|
|
105
|
+
return this.create(ctx);
|
|
106
|
+
if (!THREAD_ID_PATTERN.test(id)) {
|
|
107
|
+
throw new VendoError("validation", "threadId is malformed");
|
|
108
|
+
}
|
|
109
|
+
// ONE ownership-blind read is the friendly fast-path (03 §5). vendo_threads is
|
|
110
|
+
// keyed by the bare id, so a foreign row reads as non-null here; reusing the id
|
|
111
|
+
// would let persist() take it over. Free id → create; ours → return; anyone
|
|
112
|
+
// else's (or unparseable) → conflict. The store's guarded upsert is the real,
|
|
113
|
+
// atomic guarantee — this only surfaces the conflict early with a clear error.
|
|
114
|
+
// Ephemeral principals resolve through this exact path too (02-store §4: no
|
|
115
|
+
// overlay, ordinary rows under their subject) — nothing here is BYO-specific.
|
|
116
|
+
const record = await this.store.records(THREAD_COLLECTION).get(id);
|
|
117
|
+
if (record === null)
|
|
118
|
+
return this.create(ctx, id);
|
|
119
|
+
const thread = threadFromRecord(record);
|
|
120
|
+
if (thread !== null && thread.subject === ctx.principal.subject)
|
|
121
|
+
return thread;
|
|
122
|
+
throw new VendoError("conflict", "threadId is already in use");
|
|
123
|
+
}
|
|
124
|
+
async get(id, ctx) {
|
|
125
|
+
if (!THREAD_ID_PATTERN.test(id))
|
|
126
|
+
return null;
|
|
127
|
+
// Reserved vendo_threads rows are keyed by the bare thread id (02 §2:
|
|
128
|
+
// `id` is the thread id). Subject scoping is enforced here, on read, by
|
|
129
|
+
// checking the row's subject — never returning another subject's thread.
|
|
130
|
+
const record = await this.store.records(THREAD_COLLECTION).get(id);
|
|
131
|
+
if (!record)
|
|
132
|
+
return null;
|
|
133
|
+
const thread = threadFromRecord(record);
|
|
134
|
+
if (!thread || thread.subject !== ctx.principal.subject)
|
|
135
|
+
return null;
|
|
136
|
+
return thread;
|
|
137
|
+
}
|
|
138
|
+
async list(ctx) {
|
|
139
|
+
const records = await this.listRecords({ subject: ctx.principal.subject });
|
|
140
|
+
const threads = records
|
|
141
|
+
.map(threadFromRecord)
|
|
142
|
+
.filter((thread) => thread !== null && thread.subject === ctx.principal.subject);
|
|
143
|
+
return threads
|
|
144
|
+
.map(toSummary)
|
|
145
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
146
|
+
}
|
|
147
|
+
async delete(id, ctx) {
|
|
148
|
+
if (!THREAD_ID_PATTERN.test(id))
|
|
149
|
+
return;
|
|
150
|
+
// The bare id is shared across subjects; delete only after confirming the
|
|
151
|
+
// row belongs to this subject (get() returns null otherwise), so one
|
|
152
|
+
// subject can never delete another's thread (03 §5).
|
|
153
|
+
const existing = await this.get(id, ctx);
|
|
154
|
+
if (existing === null)
|
|
155
|
+
return;
|
|
156
|
+
await this.store.records(THREAD_COLLECTION).delete(id);
|
|
157
|
+
}
|
|
158
|
+
async persist(thread, messages) {
|
|
159
|
+
// ai-SDK UIMessages carry explicit `undefined`-valued optional props on
|
|
160
|
+
// tool parts (e.g. an approval-requested part with no output yet). The
|
|
161
|
+
// store seam is typed `Json` and rejects `undefined` values, so serialize
|
|
162
|
+
// to plain JSON — dropping absent-anyway keys — before it crosses.
|
|
163
|
+
const turnMessages = toPlainJson(messages);
|
|
164
|
+
const records = this.store.records(THREAD_COLLECTION);
|
|
165
|
+
// Guarded write via the store's atomic capability (01 §12 / 02-store §4):
|
|
166
|
+
// insert-if-absent for a first turn, revision CAS for an existing row —
|
|
167
|
+
// exactly one concurrent writer lands per attempt; the loser re-reads and
|
|
168
|
+
// re-merges (ENG-310 / AGENT-9: two overlapping turns on one thread each
|
|
169
|
+
// finish with their OWN copy of the history, so persist is read-merge-write,
|
|
170
|
+
// never a blind overwrite). No fallback: v2 has no adapter or row that
|
|
171
|
+
// predates the capability (kill-list B5 — same reasoning as the crypto v1
|
|
172
|
+
// cut), so an adapter that omits `atomic` fails closed rather than risking
|
|
173
|
+
// a lost update under concurrent writers.
|
|
174
|
+
const atomic = records.atomic;
|
|
175
|
+
if (atomic === undefined) {
|
|
176
|
+
throw new VendoError("not-implemented", "thread persistence needs a store with atomic record claims (02-store §4); this adapter omits the capability");
|
|
177
|
+
}
|
|
178
|
+
for (let attempt = 0; attempt < MAX_PERSIST_ATTEMPTS; attempt += 1) {
|
|
179
|
+
const record = await records.get(thread.id);
|
|
180
|
+
const current = record === null ? null : threadFromRecord(record);
|
|
181
|
+
if (current !== null && current.subject !== thread.subject) {
|
|
182
|
+
// Mirror the door's guarded upsert (03 §5): never take over a foreign row.
|
|
183
|
+
throw new VendoError("conflict", "threadId is already in use");
|
|
184
|
+
}
|
|
185
|
+
const updated = this.updatedThread(thread, current === null ? turnMessages : mergeMessages(current.messages, turnMessages));
|
|
186
|
+
const input = { id: updated.id, data: updated, refs: { subject: updated.subject } };
|
|
187
|
+
const written = record === null
|
|
188
|
+
? await atomic.insertIfAbsent(input)
|
|
189
|
+
: await atomic.compareAndSwap(input, record.revision);
|
|
190
|
+
if (written !== null)
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
throw new VendoError("conflict", `thread ${thread.id} persist lost the update race ${MAX_PERSIST_ATTEMPTS} times`);
|
|
194
|
+
}
|
|
195
|
+
/** The Thread as it should be written for this turn: merged messages, a
|
|
196
|
+
* freshly-derived listing title (never a stale prior title — `list` reads it
|
|
197
|
+
* back without loading messages), and a new updatedAt. */
|
|
198
|
+
updatedThread(thread, messages) {
|
|
199
|
+
return {
|
|
200
|
+
...thread,
|
|
201
|
+
messages,
|
|
202
|
+
title: deriveTitle(messages),
|
|
203
|
+
updatedAt: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// create() never writes: it hands back a fresh, not-yet-persisted Thread.
|
|
207
|
+
// The first persist() call is what puts the row in the store — same for
|
|
208
|
+
// every composition, so a get()/list() before that first persist correctly
|
|
209
|
+
// sees nothing yet, exactly as a store-backed agent always has.
|
|
210
|
+
create(ctx, requestedId) {
|
|
211
|
+
const now = new Date().toISOString();
|
|
212
|
+
return {
|
|
213
|
+
id: requestedId ?? mintThreadId(),
|
|
214
|
+
subject: ctx.principal.subject,
|
|
215
|
+
messages: [],
|
|
216
|
+
createdAt: now,
|
|
217
|
+
updatedAt: now,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/** AGENT-11 / ENG-237: drop a subject's threads from the store on session
|
|
221
|
+
* eviction. Store-backed: a no-op — the store's own TTL sweep already erased
|
|
222
|
+
* the rows (02-store §4 erase cascade) before the umbrella calls this, so the
|
|
223
|
+
* list below simply finds none. Internal-default (no `store` configured):
|
|
224
|
+
* the ONLY place those rows get reclaimed, since nothing else sweeps them.
|
|
225
|
+
* Returns the evicted thread ids so callers can release any per-thread state
|
|
226
|
+
* keyed by id (ENG-252 loadouts) — otherwise a reused `thr_*` id would inherit
|
|
227
|
+
* the evicted thread's searched-in tools. */
|
|
228
|
+
async evictSubject(subject) {
|
|
229
|
+
const records = await this.listRecords({ subject });
|
|
230
|
+
const ids = records.map((record) => record.id);
|
|
231
|
+
await Promise.all(ids.map((id) => this.store.records(THREAD_COLLECTION).delete(id)));
|
|
232
|
+
return ids;
|
|
233
|
+
}
|
|
234
|
+
/** Follows the store's pagination cursor to exhaustion (it pages at 100) —
|
|
235
|
+
* otherwise a subject's >100th thread, possibly their most recently active,
|
|
236
|
+
* would silently vanish from `list`/`evictSubject`. */
|
|
237
|
+
async listRecords(refs) {
|
|
238
|
+
const records = [];
|
|
239
|
+
let cursor;
|
|
240
|
+
do {
|
|
241
|
+
const result = await this.store.records(THREAD_COLLECTION).list({
|
|
242
|
+
refs,
|
|
243
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
244
|
+
});
|
|
245
|
+
records.push(...result.records);
|
|
246
|
+
cursor = result.cursor;
|
|
247
|
+
} while (cursor !== undefined);
|
|
248
|
+
return records;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=threads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threads.js","sourceRoot":"","sources":["../src/threads.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AgentRunReport, Principal, VendoToolEnvelope } from "@vendoai/core";
|
|
2
|
+
/**
|
|
3
|
+
* Existing-agents contract — the public tool-pack surface a BYO agent loop
|
|
4
|
+
* gets from the umbrella's `./ai-sdk` and `./mastra` subpaths. Wave 0 freezes
|
|
5
|
+
* the names and option shapes here (the docs, examples, and both shims build
|
|
6
|
+
* against them); Wave 1 Lane A supplies the implementation behind them.
|
|
7
|
+
* Frozen in `docs/superpowers/specs/2026-07-20-existing-agents-contracts.md`.
|
|
8
|
+
*/
|
|
9
|
+
/** Every pack tool is namespaced under this prefix to avoid collisions with
|
|
10
|
+
* the host loop's own tools: a registered host tool `host_x` ships as
|
|
11
|
+
* `vendo_host_x`; the built-ins below are already prefixed. */
|
|
12
|
+
export declare const VENDO_TOOL_PACK_PREFIX: "vendo_";
|
|
13
|
+
/** Generate UI. Returns fast with a `vendo/app-ref@1`; the build streams over
|
|
14
|
+
* the wire, so the host loop is never blocked on generation. */
|
|
15
|
+
export declare const VENDO_CREATE_APP_TOOL: "vendo_create_app";
|
|
16
|
+
/** Whole-task delegation via `agent.asRunner()`; returns `VendoDelegateResult`. */
|
|
17
|
+
export declare const VENDO_DELEGATE_TOOL: "vendo_delegate";
|
|
18
|
+
/** Pack filtering, matched against FINAL (namespaced) tool names. The static
|
|
19
|
+
* Mastra shim accepts exactly this (its principal resolves lazily per call
|
|
20
|
+
* from the framework's runtime context). */
|
|
21
|
+
export interface VendoToolPackFilter {
|
|
22
|
+
include?: string[];
|
|
23
|
+
exclude?: string[];
|
|
24
|
+
}
|
|
25
|
+
/** What the per-request AI SDK shim accepts: tool execution needs a
|
|
26
|
+
* principal-scoped RunContext, so the pack is built per request. */
|
|
27
|
+
export interface VendoToolPackOptions extends VendoToolPackFilter {
|
|
28
|
+
principal: Principal;
|
|
29
|
+
/** RunContext requires a session id; pass the host session's for audit
|
|
30
|
+
* continuity, or leave unset and the shim mints one per pack build. */
|
|
31
|
+
sessionId?: string;
|
|
32
|
+
}
|
|
33
|
+
/** `vendo_delegate`'s output is plain data (no embed): the run report's
|
|
34
|
+
* status and summary, plus envelope refs to anything the delegated run
|
|
35
|
+
* produced — the host renders each ref with `<VendoToolResult>` if it wants. */
|
|
36
|
+
export interface VendoDelegateResult {
|
|
37
|
+
status: AgentRunReport["status"];
|
|
38
|
+
summary: string;
|
|
39
|
+
refs: VendoToolEnvelope[];
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=tool-pack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-pack.d.ts","sourceRoot":"","sources":["../src/tool-pack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElF;;;;;;GAMG;AAEH;;gEAEgE;AAChE,eAAO,MAAM,sBAAsB,EAAG,QAAiB,CAAC;AAExD;iEACiE;AACjE,eAAO,MAAM,qBAAqB,EAAG,kBAA2B,CAAC;AAEjE,mFAAmF;AACnF,eAAO,MAAM,mBAAmB,EAAG,gBAAyB,CAAC;AAE7D;;6CAE6C;AAC7C,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;qEACqE;AACrE,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D,SAAS,EAAE,SAAS,CAAC;IACrB;4EACwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;iFAEiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,iBAAiB,EAAE,CAAC;CAC3B"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Existing-agents contract — the public tool-pack surface a BYO agent loop
|
|
3
|
+
* gets from the umbrella's `./ai-sdk` and `./mastra` subpaths. Wave 0 freezes
|
|
4
|
+
* the names and option shapes here (the docs, examples, and both shims build
|
|
5
|
+
* against them); Wave 1 Lane A supplies the implementation behind them.
|
|
6
|
+
* Frozen in `docs/superpowers/specs/2026-07-20-existing-agents-contracts.md`.
|
|
7
|
+
*/
|
|
8
|
+
/** Every pack tool is namespaced under this prefix to avoid collisions with
|
|
9
|
+
* the host loop's own tools: a registered host tool `host_x` ships as
|
|
10
|
+
* `vendo_host_x`; the built-ins below are already prefixed. */
|
|
11
|
+
export const VENDO_TOOL_PACK_PREFIX = "vendo_";
|
|
12
|
+
/** Generate UI. Returns fast with a `vendo/app-ref@1`; the build streams over
|
|
13
|
+
* the wire, so the host loop is never blocked on generation. */
|
|
14
|
+
export const VENDO_CREATE_APP_TOOL = "vendo_create_app";
|
|
15
|
+
/** Whole-task delegation via `agent.asRunner()`; returns `VendoDelegateResult`. */
|
|
16
|
+
export const VENDO_DELEGATE_TOOL = "vendo_delegate";
|
|
17
|
+
//# sourceMappingURL=tool-pack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-pack.js","sourceRoot":"","sources":["../src/tool-pack.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH;;gEAEgE;AAChE,MAAM,CAAC,MAAM,sBAAsB,GAAG,QAAiB,CAAC;AAExD;iEACiE;AACjE,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAA2B,CAAC;AAEjE,mFAAmF;AACnF,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAyB,CAAC"}
|