basion-ai-sdk 0.14.0 → 0.14.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/dist/extensions/vercel-ai.d.ts +11 -0
- package/dist/extensions/vercel-ai.js +36 -0
- package/dist/extensions/vercel-ai.js.map +1 -1
- package/dist/extensions/vfs-sync.js +3 -0
- package/dist/extensions/vfs-sync.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agent-state-client.ts +29 -0
- package/src/extensions/vercel-ai.ts +15 -0
- package/src/extensions/vfs-sync.ts +15 -6
|
@@ -80,6 +80,17 @@ declare class VercelAIMessageStore {
|
|
|
80
80
|
* @param conversationId - The conversation ID to clear history for
|
|
81
81
|
*/
|
|
82
82
|
clear(conversationId: string): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Prune messages before a given sequence number.
|
|
85
|
+
*
|
|
86
|
+
* Called after server-side compaction fires. Pruned messages are excluded
|
|
87
|
+
* from `load()` but retained in the database for audit.
|
|
88
|
+
*
|
|
89
|
+
* @param conversationId - The conversation ID
|
|
90
|
+
* @param beforeSequence - Prune all messages with sequence < this value
|
|
91
|
+
* @returns Number of messages pruned
|
|
92
|
+
*/
|
|
93
|
+
pruneBeforeSequence(conversationId: string, beforeSequence: number): Promise<number>;
|
|
83
94
|
/**
|
|
84
95
|
* Get the last N messages from history.
|
|
85
96
|
*
|
|
@@ -155,6 +155,28 @@ var AgentStateClient = class {
|
|
|
155
155
|
throw new APIException(`Failed to get vercel messages: ${error}`);
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Prune Vercel AI messages before a given sequence number.
|
|
160
|
+
* Pruned messages are excluded from load but retained for audit.
|
|
161
|
+
*/
|
|
162
|
+
async pruneVercelMessages(conversationId, beforeSequence) {
|
|
163
|
+
const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}/prune`;
|
|
164
|
+
try {
|
|
165
|
+
const response = await fetch(url, {
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: { "Content-Type": "application/json" },
|
|
168
|
+
body: JSON.stringify({ before_sequence: beforeSequence })
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
const errorText = await response.text();
|
|
172
|
+
throw new APIException(`Failed to prune vercel messages: ${response.status} - ${errorText}`, response.status);
|
|
173
|
+
}
|
|
174
|
+
return await response.json();
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (error instanceof APIException) throw error;
|
|
177
|
+
throw new APIException(`Failed to prune vercel messages: ${error}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
158
180
|
/**
|
|
159
181
|
* Delete all Vercel AI messages for a conversation.
|
|
160
182
|
*/
|
|
@@ -267,6 +289,20 @@ var VercelAIMessageStore = class {
|
|
|
267
289
|
async clear(conversationId) {
|
|
268
290
|
await this.client.deleteVercelMessages(conversationId);
|
|
269
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Prune messages before a given sequence number.
|
|
294
|
+
*
|
|
295
|
+
* Called after server-side compaction fires. Pruned messages are excluded
|
|
296
|
+
* from `load()` but retained in the database for audit.
|
|
297
|
+
*
|
|
298
|
+
* @param conversationId - The conversation ID
|
|
299
|
+
* @param beforeSequence - Prune all messages with sequence < this value
|
|
300
|
+
* @returns Number of messages pruned
|
|
301
|
+
*/
|
|
302
|
+
async pruneBeforeSequence(conversationId, beforeSequence) {
|
|
303
|
+
const result = await this.client.pruneVercelMessages(conversationId, beforeSequence);
|
|
304
|
+
return result.pruned;
|
|
305
|
+
}
|
|
270
306
|
/**
|
|
271
307
|
* Get the last N messages from history.
|
|
272
308
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exceptions.ts","../../src/types.ts","../../src/agent.ts","../../src/agent-state-client.ts","../../src/extensions/vercel-ai.ts"],"names":[],"mappings":";;;;;;;;AAQO,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA,EACxC,YAAY,OAAA,EAAiB;AACzB,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AACZ,IAAA,KAAA,CAAM,iBAAA,GAAoB,IAAA,EAAM,IAAA,CAAK,WAAW,CAAA;AAAA,EACpD;AACJ,CAAA;AAmCO,IAAM,YAAA,GAAN,cAA2B,gBAAA,CAAiB;AAAA,EAC/C,UAAA;AAAA,EAEA,WAAA,CAAY,SAAiB,UAAA,EAAqB;AAC9C,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,UAAA,GAAa,UAAA;AAAA,EACtB;AACJ,CAAA;ACyDoC,EAAE,MAAA,CAAO;AAAA,EACzC,GAAA,EAAK,EAAE,MAAA,EAAO;AAAA,EACd,QAAA,EAAU,EAAE,MAAA,EAAO;AAAA,EACnB,WAAA,EAAa,EAAE,MAAA,EAAO;AAAA,EACtB,IAAA,EAAM,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC1B,QAAA,EAAU,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS;AAAA,EAC9B,SAAA,EAAW,CAAA,CAAE,MAAA,EAAO,CAAE,QAAA;AAC1B,CAAC;;;AChFD,IAAM,UAAA,mBAAa,MAAA,CAAO,GAAA,CAAI,8BAA8B,CAAA;AAC5D,IAAI,CAAE,UAAA,CAAuC,UAAU,CAAA,EAAG;AACtD,EAAC,UAAA,CAAuC,UAAU,CAAA,GAAI,IAAI,iBAAA,EAGvD;AACP;AACO,IAAM,eAAA,GAAmB,WAAuC,UAAU,CAAA;;;AC7B1E,IAAM,mBAAN,MAAuB;AAAA,EACT,OAAA;AAAA,EAEjB,YAAY,OAAA,EAAiB;AACzB,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,GAAA,CACF,cAAA,EACA,SAAA,EACA,KAAA,EACwE;AACxE,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,gBAAgB,cAAc,CAAA,CAAA;AACzD,IAAA,MAAM,OAAA,GAAU;AAAA,MACZ,SAAA;AAAA,MACA;AAAA,KACJ;AAEA,IAAA,IAAI;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAC9B,MAAA,EAAQ,KAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,QAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,OAC/B,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,MAAM,IAAI,aAAa,CAAA,6BAAA,EAAgC,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,MAC5G;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC/B,SAAS,KAAA,EAAO;AACZ,MAAA,IAAI,KAAA,YAAiB,cAAc,MAAM,KAAA;AACzC,MAAA,MAAM,IAAI,YAAA,CAAa,CAAA,6BAAA,EAAgC,KAAK,CAAA,CAAE,CAAA;AAAA,IAClE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,GAAA,CACF,cAAA,EACA,SAAA,EACkC;AAClC,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,CAAA,EAAG,KAAK,OAAO,CAAA,aAAA,EAAgB,cAAc,CAAA,CAAE,CAAA;AACnE,IAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,WAAA,EAAa,SAAS,CAAA;AAE3C,IAAA,IAAI;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,CAAI,UAAU,CAAA;AAE3C,MAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AACzB,QAAA,OAAO,IAAA;AAAA,MACX;AAEA,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,MAAM,IAAI,aAAa,CAAA,2BAAA,EAA8B,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,MAC1G;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC/B,SAAS,KAAA,EAAO;AACZ,MAAA,IAAI,KAAA,YAAiB,cAAc,MAAM,KAAA;AACzC,MAAA,MAAM,IAAI,YAAA,CAAa,CAAA,2BAAA,EAA8B,KAAK,CAAA,CAAE,CAAA;AAAA,IAChE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAA,CACF,cAAA,EACA,SAAA,EACwE;AACxE,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,CAAA,EAAG,KAAK,OAAO,CAAA,aAAA,EAAgB,cAAc,CAAA,CAAE,CAAA;AACnE,IAAA,IAAI,SAAA,EAAW;AACX,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,WAAA,EAAa,SAAS,CAAA;AAAA,IAC/C;AAEA,IAAA,IAAI;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,CAAI,UAAS,EAAG,EAAE,MAAA,EAAQ,QAAA,EAAU,CAAA;AAEjE,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,MAAM,IAAI,aAAa,CAAA,8BAAA,EAAiC,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,MAC7G;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC/B,SAAS,KAAA,EAAO;AACZ,MAAA,IAAI,KAAA,YAAiB,cAAc,MAAM,KAAA;AACzC,MAAA,MAAM,IAAI,YAAA,CAAa,CAAA,8BAAA,EAAiC,KAAK,CAAA,CAAE,CAAA;AAAA,IACnE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,oBAAA,CACF,cAAA,EACA,QAAA,EACqE;AACrE,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,uBAAuB,cAAc,CAAA,OAAA,CAAA;AAEhE,IAAA,IAAI;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAC9B,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,QAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,UAAU;AAAA,OACpC,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,MAAM,IAAI,aAAa,CAAA,kCAAA,EAAqC,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,MACjH;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC/B,SAAS,KAAA,EAAO;AACZ,MAAA,IAAI,KAAA,YAAiB,cAAc,MAAM,KAAA;AACzC,MAAA,MAAM,IAAI,YAAA,CAAa,CAAA,kCAAA,EAAqC,KAAK,CAAA,CAAE,CAAA;AAAA,IACvE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,iBAAA,CACF,cAAA,EACA,IAAA,EACkC;AAClC,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,CAAA,EAAG,KAAK,OAAO,CAAA,oBAAA,EAAuB,cAAc,CAAA,CAAE,CAAA;AAC1E,IAAA,IAAI,SAAS,MAAA,EAAW;AACpB,MAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,MAAA,EAAQ,MAAA,CAAO,IAAI,CAAC,CAAA;AAAA,IAC7C;AAEA,IAAA,IAAI;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,CAAI,UAAU,CAAA;AAE3C,MAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AACzB,QAAA,OAAO,IAAA;AAAA,MACX;AAEA,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,MAAM,IAAI,aAAa,CAAA,+BAAA,EAAkC,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,MAC9G;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC/B,SAAS,KAAA,EAAO;AACZ,MAAA,IAAI,KAAA,YAAiB,cAAc,MAAM,KAAA;AACzC,MAAA,MAAM,IAAI,YAAA,CAAa,CAAA,+BAAA,EAAkC,KAAK,CAAA,CAAE,CAAA;AAAA,IACpE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,qBACF,cAAA,EACqD;AACrD,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,uBAAuB,cAAc,CAAA,CAAA;AAEhE,IAAA,IAAI;AACA,MAAA,MAAM,WAAW,MAAM,KAAA,CAAM,KAAK,EAAE,MAAA,EAAQ,UAAU,CAAA;AAEtD,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,MAAM,IAAI,aAAa,CAAA,kCAAA,EAAqC,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,MACjH;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC/B,SAAS,KAAA,EAAO;AACZ,MAAA,IAAI,KAAA,YAAiB,cAAc,MAAM,KAAA;AACzC,MAAA,MAAM,IAAI,YAAA,CAAa,CAAA,kCAAA,EAAqC,KAAK,CAAA,CAAE,CAAA;AAAA,IACvE;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,yBAAA,CACF,cAAA,EACA,SAAA,EACqE;AACrE,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,yBAAyB,cAAc,CAAA,OAAA,CAAA;AAElE,IAAA,IAAI;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAC9B,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,QAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,WAAW;AAAA,OACrC,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,MAAM,IAAI,aAAa,CAAA,uCAAA,EAA0C,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,MACtH;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC/B,SAAS,KAAA,EAAO;AACZ,MAAA,IAAI,KAAA,YAAiB,cAAc,MAAM,KAAA;AACzC,MAAA,MAAM,IAAI,YAAA,CAAa,CAAA,uCAAA,EAA0C,KAAK,CAAA,CAAE,CAAA;AAAA,IAC5E;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,uBACF,cAAA,EACkC;AAClC,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,yBAAyB,cAAc,CAAA,CAAA;AAElE,IAAA,IAAI;AACA,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,CAAI,UAAU,CAAA;AAE3C,MAAA,IAAI,QAAA,CAAS,WAAW,GAAA,EAAK;AACzB,QAAA,OAAO,IAAA;AAAA,MACX;AAEA,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,QAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,QAAA,MAAM,IAAI,aAAa,CAAA,oCAAA,EAAuC,QAAA,CAAS,MAAM,CAAA,GAAA,EAAM,SAAS,CAAA,CAAA,EAAI,QAAA,CAAS,MAAM,CAAA;AAAA,MACnH;AAEA,MAAA,OAAO,MAAM,SAAS,IAAA,EAAK;AAAA,IAC/B,SAAS,KAAA,EAAO;AACZ,MAAA,IAAI,KAAA,YAAiB,cAAc,MAAM,KAAA;AACzC,MAAA,MAAM,IAAI,YAAA,CAAa,CAAA,oCAAA,EAAuC,KAAK,CAAA,CAAE,CAAA;AAAA,IACzE;AAAA,EACJ;AACJ,CAAA;;;AC3MO,IAAM,uBAAN,MAA2B;AAAA,EAC9B,OAAwB,SAAA,GAAY,WAAA;AAAA,EACnB,MAAA;AAAA,EAEjB,YAAY,GAAA,EAAqB;AAC7B,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,gBAAA,CAAiB,GAAA,CAAI,cAAc,oBAAoB,CAAA;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,KAAK,cAAA,EAAgD;AACvD,IAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,MAAA,CAAO,kBAAkB,cAAc,CAAA;AAEjE,IAAA,IAAI,CAAC,MAAA,EAAQ;AACT,MAAA,OAAO,EAAC;AAAA,IACZ;AAEA,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,IAAS,EAAC;AAC/B,IAAA,MAAM,WAAW,KAAA,CAAM,QAAA;AAEvB,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC1B,MAAA,OAAO,EAAC;AAAA,IACZ;AAEA,IAAA,OAAO,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,cAAc,CAAA;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,MAAA,CAAO,cAAA,EAAwB,WAAA,EAA2C;AAC5E,IAAA,MAAM,GAAA,GAAM,gBAAgB,QAAA,EAAS;AACrC,IAAA,MAAM,SAAS,GAAA,GAAM,IAAA,CAAK,eAAA,CAAgB,WAAA,EAAa,GAAG,CAAA,GAAI,WAAA;AAC9D,IAAA,MAAM,KAAK,MAAA,CAAO,oBAAA;AAAA,MACd,cAAA;AAAA,MACA,MAAA,CAAO,GAAA,CAAI,IAAA,CAAK,gBAAgB;AAAA,KACpC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAM,cAAA,EAAuC;AAC/C,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,oBAAA,CAAqB,cAAc,CAAA;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,eAAA,CAAgB,cAAA,EAAwB,KAAA,EAAuC;AACjF,IAAA,MAAM,SAAS,MAAM,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,gBAAgB,KAAK,CAAA;AAExE,IAAA,IAAI,CAAC,MAAA,EAAQ;AACT,MAAA,OAAO,EAAC;AAAA,IACZ;AAEA,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,IAAS,EAAC;AAC/B,IAAA,MAAM,WAAW,KAAA,CAAM,QAAA;AAEvB,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC1B,MAAA,OAAO,EAAC;AAAA,IACZ;AAEA,IAAA,OAAO,QAAA,CAAS,MAAA,CAAO,IAAA,CAAK,cAAc,CAAA;AAAA,EAC9C;AAAA,EAEQ,eAAA,CACJ,UACA,GAAA,EACa;AACb,IAAA,MAAM,MAAA,GAAS,CAAC,GAAG,QAAQ,CAAA;AAE3B,IAAA,IAAI,IAAI,aAAA,EAAe;AACnB,MAAA,MAAM,UAAU,MAAA,CAAO,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,MAAM,CAAA;AACzD,MAAA,IAAI,YAAY,EAAA,EAAI;AAChB,QAAA,MAAA,CAAO,OAAO,IAAI,EAAE,GAAG,OAAO,OAAO,CAAA,EAAG,UAAA,EAAY,GAAA,CAAI,aAAA,EAAc;AAAA,MAC1E;AAAA,IACJ;AAEA,IAAA,IAAI,IAAI,iBAAA,EAAmB;AACvB,MAAA,KAAA,IAAS,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AACzC,QAAA,MAAM,CAAA,GAAI,OAAO,CAAC,CAAA;AAClB,QAAA,IACI,EAAE,IAAA,KAAS,WAAA,KACV,OAAO,CAAA,CAAE,OAAA,KAAY,WAChB,CAAA,CAAE,OAAA,CAAQ,SAAS,CAAA,GAClB,CAAA,CAAE,QAAoC,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,IAAA,KAAS,MAAM,CAAA,CAAA,EAC5E;AACE,UAAA,MAAA,CAAO,CAAC,IAAI,EAAE,GAAG,OAAO,CAAC,CAAA,EAAG,UAAA,EAAY,GAAA,CAAI,iBAAA,EAAkB;AAC9D,UAAA;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,IAAA,OAAO,MAAA;AAAA,EACX;AAAA,EAEQ,eAAe,GAAA,EAAkC;AACrD,IAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,KAAQ,MAAM,OAAO,KAAA;AACpD,IAAA,MAAM,CAAA,GAAI,GAAA;AACV,IAAA,OACI,OAAO,EAAE,IAAA,KAAS,QAAA,IAClB,CAAC,QAAA,EAAU,MAAA,EAAQ,aAAa,MAAM,CAAA,CAAE,SAAS,CAAA,CAAE,IAAI,MACtD,OAAO,CAAA,CAAE,YAAY,QAAA,IAAY,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,OAAO,CAAA,CAAA;AAAA,EAEjE;AAAA,EAEQ,iBAAiB,GAAA,EAA2C;AAChE,IAAA,OAAO,EAAE,GAAG,GAAA,EAAI;AAAA,EACpB;AACJ","file":"vercel-ai.js","sourcesContent":["/**\n * Basion Agent SDK - Custom Errors\n * Port of Python basion_agent/exceptions.py\n */\n\n/**\n * Base error class for Basion Agent SDK\n */\nexport class BasionAgentError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'BasionAgentError'\n Error.captureStackTrace?.(this, this.constructor)\n }\n}\n\n/**\n * Error during agent registration with AI Inventory\n */\nexport class RegistrationError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'RegistrationError'\n }\n}\n\n/**\n * Error during Kafka operations (produce/consume)\n */\nexport class KafkaError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'KafkaError'\n }\n}\n\n/**\n * Error in SDK configuration\n */\nexport class ConfigurationError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigurationError'\n }\n}\n\n/**\n * Error from API calls to backend services\n */\nexport class APIException extends BasionAgentError {\n statusCode?: number\n\n constructor(message: string, statusCode?: number) {\n super(message)\n this.name = 'APIException'\n this.statusCode = statusCode\n }\n}\n\n/**\n * Error during heartbeat operations\n */\nexport class HeartbeatError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'HeartbeatError'\n }\n}\n\n/**\n * Error during gRPC connection/communication\n */\nexport class GatewayError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'GatewayError'\n }\n}\n\n/**\n * Error during reconnection attempts\n */\nexport class ReconnectionError extends BasionAgentError {\n /** Number of attempts made before giving up */\n attempts: number\n /** The last error that occurred during reconnection */\n lastError?: Error\n\n constructor(message: string, attempts: number, lastError?: Error) {\n super(message)\n this.name = 'ReconnectionError'\n this.attempts = attempts\n this.lastError = lastError\n }\n}\n","/**\n * Basion Agent SDK - Shared Types\n * Port of Python basion_agent types\n */\n\nimport { z } from 'zod'\n\n// ============================================================================\n// Kafka Message Types\n// ============================================================================\n\nexport interface KafkaMessage {\n topic: string\n partition: number\n offset: number\n key: string\n headers: Record<string, string>\n body: Record<string, unknown>\n timestamp: number\n}\n\nexport interface KafkaHeaders {\n conversationId?: string\n userId?: string\n from_?: string\n to_?: string\n messageMetadata?: string\n messageSchema?: string\n /** @deprecated Use attachments (plural) instead. Kept for backward compatibility. */\n attachment?: string\n /** JSON-stringified array of AttachmentInfo objects */\n attachments?: string\n /** UUID of the user message in conversation-store (for cross-system mapping) */\n userMessageId?: string\n /** Pre-generated UUID for the assistant response in conversation-store (for cross-system mapping) */\n assistantMessageId?: string\n [key: string]: string | undefined\n}\n\n// ============================================================================\n// Agent Registration Types\n// ============================================================================\n\nexport interface AgentRegistrationData {\n id: string\n name: string\n about: string\n document: string\n representationName?: string\n baseUrl?: string\n metadata?: Record<string, unknown>\n relatedPages?: RelatedPage[]\n}\n\nexport interface RelatedPage {\n name: string\n endpoint: string\n}\n\nexport interface Prompt {\n label: string\n prompt: string\n}\n\nexport interface RegisterOptions {\n name: string\n about: string\n document: string\n representationName?: string\n baseUrl?: string\n metadata?: Record<string, unknown>\n relatedPages?: RelatedPage[]\n categoryNames?: string[]\n prompts?: Prompt[]\n detailedDescription?: string\n version?: string\n lifecycleStage?: 'dev' | 'test' | 'private-preview' | 'public-preview' | 'public'\n welcomeMessage?: string\n waitlist?: boolean\n forceUpdate?: boolean\n}\n\n// ============================================================================\n// Message Types\n// ============================================================================\n\n// Forward reference - actual Message class is imported where needed\n// Using 'any' here to avoid circular dependency, but consumers should use Message type\nexport interface MessageHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (message: any, sender: string): void | Promise<void>\n}\n\nexport interface SenderFilterOptions {\n senders?: string[]\n}\n\n// ============================================================================\n// Streamer Types\n// ============================================================================\n\nexport interface StreamOptions {\n persist?: boolean\n eventType?: string\n}\n\nexport interface StreamerOptions {\n awaiting?: boolean\n}\n\n// ============================================================================\n// Attachment Types\n// ============================================================================\n\nexport const AttachmentInfoSchema = z.object({\n url: z.string(),\n filename: z.string(),\n contentType: z.string(),\n size: z.number().optional(),\n fileType: z.string().optional(),\n objectKey: z.string().optional(),\n})\n\nexport type AttachmentInfo = z.infer<typeof AttachmentInfoSchema>\n\nexport function isImageAttachment(attachment: AttachmentInfo): boolean {\n return attachment.contentType?.startsWith('image/') || false\n}\n\nexport function isPdfAttachment(attachment: AttachmentInfo): boolean {\n return attachment.contentType === 'application/pdf'\n}\n\n// ============================================================================\n// Conversation Message Types\n// ============================================================================\n\nexport interface ConversationMessageData {\n id: string\n conversationId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n from?: string\n to?: string\n createdAt: string\n metadata?: Record<string, unknown>\n}\n\n// ============================================================================\n// Gateway Client Types\n// ============================================================================\n\nexport interface ProduceAck {\n topic: string\n partition: number\n offset: number\n correlationId?: string\n}\n\n// ============================================================================\n// Reconnection Types\n// ============================================================================\n\n/**\n * Configuration options for automatic reconnection behavior.\n */\nexport interface ReconnectionOptions {\n /** Maximum number of reconnection attempts before giving up. Default: 10 */\n maxRetries?: number\n /** Initial delay in milliseconds before first retry. Default: 1000 */\n initialDelayMs?: number\n /** Maximum delay in milliseconds between retries. Default: 30000 */\n maxDelayMs?: number\n /** Multiplier for exponential backoff. Default: 2 */\n backoffMultiplier?: number\n /** Whether to automatically reconnect on disconnect. Default: true */\n autoReconnect?: boolean\n}\n\n/**\n * Connection state for the gateway client.\n */\nexport type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'\n\n/**\n * Listener callback for connection state changes.\n */\nexport interface ConnectionStateListener {\n (state: ConnectionState, error?: Error): void\n}\n\n/**\n * Default reconnection options.\n */\nexport const DEFAULT_RECONNECTION_OPTIONS: Required<ReconnectionOptions> = {\n maxRetries: 10,\n initialDelayMs: 1000,\n maxDelayMs: 30000,\n backoffMultiplier: 2,\n autoReconnect: true,\n}\n\n// ============================================================================\n// App Configuration Types\n// ============================================================================\n\nexport interface AppOptions {\n gatewayUrl: string\n apiKey: string\n heartbeatInterval?: number\n maxConcurrentTasks?: number\n errorMessageTemplate?: string\n secure?: boolean\n /** Reconnection options for the gateway connection */\n reconnection?: ReconnectionOptions\n /** Enable remote logging to Loki via the gateway (default: false) */\n enableRemoteLogging?: boolean\n /** Minimum log level for remote logging (default: 'info') */\n remoteLogLevel?: 'debug' | 'info' | 'warn' | 'error'\n /** Number of logs to batch before sending (default: 100) */\n remoteLogBatchSize?: number\n /** Maximum seconds between log flushes (default: 5.0) */\n remoteLogFlushInterval?: number\n /** Sentry configuration (optional — omit or leave dsn empty to disable) */\n sentry?: import('./sentry.js').SentryConfig\n /** Braintrust configuration (optional — omit or leave apiKey empty to disable) */\n braintrust?: import('./braintrust.js').BraintrustConfig\n}\n\n// ============================================================================\n// HTTP Response Types\n// ============================================================================\n\nexport interface ConversationData {\n id: string\n userId: string\n agentId?: string\n awaitingRoute?: string\n pendingResponseSchema?: Record<string, unknown>\n createdAt: string\n updatedAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface GetMessagesOptions {\n role?: string\n from?: string\n to?: string\n limit?: number\n offset?: number\n}\n\n// ============================================================================\n// Memory Types\n// ============================================================================\n\n/**\n * A message stored in ai-memory.\n * Matches the ai-memory API MessageStored schema.\n */\nexport interface MemoryMessage {\n id?: string\n role: 'user' | 'assistant' | 'system'\n content: string\n tenantId?: string\n userId?: string\n conversationId?: string\n metadata?: Record<string, unknown>\n timestamp?: string\n sequenceNumber?: number\n}\n\n/**\n * Options for memory search operations.\n */\nexport interface MemorySearchOptions {\n /** Maximum number of results (1-100, default: 10) */\n limit?: number\n /** Minimum similarity threshold (0-100, default: 70) */\n minSimilarity?: number\n /** Number of surrounding context messages to include (0-20, default: 0) */\n contextMessages?: number\n /** Tenant ID for multi-tenant filtering */\n tenantId?: string\n /** Tenant matching mode: 'exact' or 'hierarchy' */\n tenantMatch?: 'exact' | 'hierarchy'\n}\n\n/**\n * A memory search result with similarity score and optional context.\n */\nexport interface MemorySearchResult {\n message: MemoryMessage\n score: number\n context: MemoryMessage[]\n}\n\n/**\n * User conversation summary from ai-memory.\n */\nexport interface UserSummary {\n text: string\n lastUpdated: string\n messageCount: number\n version: number\n}\n","/**\n * Basion Agent SDK - Agent\n * Port of Python basion_agent/agent.py\n */\n\nimport { randomUUID } from 'crypto'\nimport { AsyncLocalStorage } from 'node:async_hooks'\n\nimport type { BasionAgentApp } from './app.js'\nimport type { AttachmentClient } from './attachment-client.js'\nimport { logBraintrustError,withBraintrustSpan } from './braintrust.js'\nimport type { ConversationClient } from './conversation-client.js'\nimport { ConfigurationError } from './exceptions.js'\nimport type { GatewayClient } from './gateway-client.js'\nimport { type HeartbeatFailureCallback,HeartbeatManager } from './heartbeat.js'\nimport type { MemoryClient } from './memory-client.js'\nimport type { MemoryV2Client } from './memory-v2-client.js'\nimport { Message } from './message.js'\nimport type { RenderClient } from './render-client.js'\nimport { captureAgentError, setConversationId,withAgentSpan } from './sentry.js'\nimport { Streamer } from './streamer.js'\nimport { Tools } from './tools/container.js'\nimport type {\n AgentRegistrationData,\n KafkaMessage,\n MessageHandler,\n SenderFilterOptions,\n StreamerOptions,\n} from './types.js'\n\n/**\n * AsyncLocalStorage context set automatically by the agent message handler.\n * VercelAIMessageStore reads from this to embed cross-system IDs without\n * requiring agent developers to pass them explicitly.\n *\n * Stored on globalThis so that the same instance is shared across all bundle\n * chunks (tsup can include this module in multiple output files; globalThis\n * ensures they all reference the same AsyncLocalStorage object).\n *\n * @internal — consumed by VercelAIMessageStore; not part of the public API.\n */\nconst _msgCtxKey = Symbol.for('basion-ai-sdk.messageContext')\nif (!(globalThis as Record<symbol, unknown>)[_msgCtxKey]) {\n (globalThis as Record<symbol, unknown>)[_msgCtxKey] = new AsyncLocalStorage<{\n userMessageId?: string\n responseMessageId?: string\n }>()\n}\nexport const _messageContext = (globalThis as Record<symbol, unknown>)[_msgCtxKey] as AsyncLocalStorage<{\n userMessageId?: string\n responseMessageId?: string\n}>\n\n/**\n * Filter for matching message senders.\n */\nexport class SenderFilter {\n private readonly _include: Set<string> = new Set()\n private readonly _exclude: Set<string> = new Set()\n readonly matchAll: boolean\n\n constructor(senders?: string[]) {\n if (!senders || senders.length === 0) {\n this.matchAll = true\n } else {\n this.matchAll = false\n for (const sender of senders) {\n if (sender.startsWith('~')) {\n this._exclude.add(sender.slice(1))\n } else {\n this._include.add(sender)\n }\n }\n }\n }\n\n /**\n * Get included senders as an array (for compatibility with Python tests).\n */\n get include(): string[] {\n return Array.from(this._include)\n }\n\n /**\n * Get excluded senders as an array (for compatibility with Python tests).\n */\n get exclude(): string[] {\n return Array.from(this._exclude)\n }\n\n /**\n * Check if sender matches this filter.\n */\n matches(sender: string): boolean {\n // If excluded, reject\n if (this._exclude.has(sender)) {\n return false\n }\n // If match all or explicitly included\n if (this.matchAll || this._include.size === 0) {\n return true\n }\n return this._include.has(sender)\n }\n}\n\n// Message handlers stored as tuples: [SenderFilter, MessageHandler]\ntype RegisteredHandler = [SenderFilter, MessageHandler]\n\n/**\n * Core agent class that handles messaging via the Agent Gateway.\n */\nexport class Agent {\n readonly name: string\n readonly agentData: AgentRegistrationData\n sendErrorResponses: boolean = true\n errorMessageTemplate: string\n\n private readonly gatewayClient: GatewayClient\n private readonly conversationClient?: ConversationClient\n private readonly memoryClient?: MemoryClient\n private readonly memoryV2Client?: MemoryV2Client\n private readonly attachmentClient?: AttachmentClient\n private readonly renderClient?: RenderClient\n private readonly heartbeatInterval: number\n private readonly heartbeatFailureThreshold: number\n private readonly onHeartbeatFailure?: HeartbeatFailureCallback\n\n /** Set by BasionAgentApp.registerMe() to enable agent.call() */\n _app?: BasionAgentApp\n\n private messageHandlers: RegisteredHandler[] = []\n private heartbeatManager?: HeartbeatManager\n private initialized: boolean = false\n private running: boolean = false\n private _tools?: Tools\n\n /** AbortControllers for cancellable conversations, keyed by conversationId */\n private _cancelControllers = new Map<string, AbortController>()\n /** Tracks cancel requests that arrived before the handler created an AbortController */\n private _cancelledConversations = new Set<string>()\n\n constructor(options: {\n name: string\n gatewayClient: GatewayClient\n agentData: AgentRegistrationData\n heartbeatInterval?: number\n heartbeatFailureThreshold?: number\n onHeartbeatFailure?: HeartbeatFailureCallback\n conversationClient?: ConversationClient\n memoryClient?: MemoryClient\n memoryV2Client?: MemoryV2Client\n attachmentClient?: AttachmentClient\n renderClient?: RenderClient\n errorMessageTemplate?: string\n }) {\n this.name = options.name\n this.gatewayClient = options.gatewayClient\n this.agentData = options.agentData\n this.heartbeatInterval = options.heartbeatInterval ?? 60\n this.heartbeatFailureThreshold = options.heartbeatFailureThreshold ?? 3\n this.onHeartbeatFailure = options.onHeartbeatFailure\n this.conversationClient = options.conversationClient\n this.memoryClient = options.memoryClient\n this.memoryV2Client = options.memoryV2Client\n this.attachmentClient = options.attachmentClient\n this.renderClient = options.renderClient\n this.errorMessageTemplate = options.errorMessageTemplate ??\n 'I encountered an error while processing your message. Please try again or contact support if the issue persists.'\n }\n\n /**\n * Create an AbortSignal for a conversation's current operation.\n * Call this before starting work (e.g. streamText) and pass the\n * returned signal so the operation can be cancelled externally.\n *\n * If a cancel request arrived before this call, the returned signal\n * will already be in the aborted state.\n */\n createAbortSignal(conversationId: string): AbortSignal {\n // Replace any existing controller (defensive cleanup)\n const existing = this._cancelControllers.get(conversationId)\n if (existing) {\n existing.abort()\n }\n\n const controller = new AbortController()\n this._cancelControllers.set(conversationId, controller)\n\n // Handle race: cancel arrived before handler created the controller\n if (this._cancelledConversations.has(conversationId)) {\n this._cancelledConversations.delete(conversationId)\n controller.abort()\n }\n\n return controller.signal\n }\n\n /**\n * Cancel the running operation for a conversation.\n * Called by the message interceptor when a cancel message arrives.\n */\n cancelConversation(conversationId: string): void {\n const controller = this._cancelControllers.get(conversationId)\n if (controller) {\n controller.abort()\n this._cancelControllers.delete(conversationId)\n } else {\n // Cancel arrived before handler started — mark for pre-abort\n this._cancelledConversations.add(conversationId)\n }\n }\n\n /**\n * Clean up the abort controller after the handler completes.\n */\n clearAbortController(conversationId: string): void {\n this._cancelControllers.delete(conversationId)\n this._cancelledConversations.delete(conversationId)\n }\n\n /**\n * Access to tools (knowledge graph, etc.).\n *\n * Example:\n * const kg = agent.tools.knowledgeGraph\n * const diseases = await kg.searchDiseases({ name: \"Huntington\" })\n */\n get tools(): Tools {\n if (!this._tools) {\n this._tools = new Tools(this.gatewayClient)\n }\n return this._tools\n }\n\n /**\n * Register a message handler.\n * \n * Usage:\n * agent.onMessage(async (message, sender) => { ... })\n * agent.onMessage(handler, { senders: ['user'] })\n */\n onMessage(handler: MessageHandler): void\n onMessage(handler: MessageHandler, options: SenderFilterOptions): void\n onMessage(\n handler: MessageHandler,\n options?: SenderFilterOptions\n ): void {\n const filter = new SenderFilter(options?.senders)\n this.messageHandlers.push([filter, handler])\n }\n\n /**\n * Initialize agent after gateway connection is established.\n * Can be called multiple times (e.g., after reconnection) to re-register handlers.\n */\n initializeWithGateway(): void {\n // Register message handler with gateway\n // This is idempotent - calling it again just updates the handler\n const topic = `${this.name}.inbox`\n this.gatewayClient.registerHandler(topic, (msg) => {\n this.handleKafkaMessage(msg)\n })\n\n // Start heartbeat if not already created\n if (!this.heartbeatManager) {\n this.heartbeatManager = new HeartbeatManager(\n this.gatewayClient,\n this.name,\n this.heartbeatInterval,\n {\n failureThreshold: this.heartbeatFailureThreshold,\n onFailureThresholdExceeded: this.handleHeartbeatFailure.bind(this),\n }\n )\n }\n\n // Start or resume heartbeat (in case it was stopped during disconnect)\n if (!this.heartbeatManager.isRunning()) {\n this.heartbeatManager.start()\n } else if (this.heartbeatManager.isPaused()) {\n this.heartbeatManager.resume()\n }\n\n this.initialized = true\n }\n\n /**\n * Handle heartbeat failure threshold exceeded.\n * This can indicate a stale connection that needs reconnection.\n */\n private handleHeartbeatFailure(consecutiveFailures: number, lastError?: Error): void {\n console.warn(\n `Agent '${this.name}' heartbeat failure threshold exceeded (${consecutiveFailures} failures)`\n )\n\n // Call custom callback if provided\n if (this.onHeartbeatFailure) {\n this.onHeartbeatFailure(consecutiveFailures, lastError)\n }\n\n // If gateway is not connected, try to trigger reconnection\n if (!this.gatewayClient.isConnected() && !this.gatewayClient.isReconnectingNow()) {\n console.warn('Gateway not connected, attempting force reconnect...')\n this.gatewayClient.forceReconnect().catch((err) => {\n console.error('Force reconnect from heartbeat failure failed:', err)\n })\n }\n }\n\n /**\n * Pause heartbeat sending (e.g., during reconnection).\n */\n pauseHeartbeat(): void {\n this.heartbeatManager?.pause()\n }\n\n /**\n * Resume heartbeat sending.\n */\n resumeHeartbeat(): void {\n this.heartbeatManager?.resume()\n }\n\n /**\n * Get sender from message headers.\n */\n private getSender(headers: Record<string, string>): string {\n return headers.from_ ?? headers.from ?? 'user'\n }\n\n /**\n * Find first handler that matches the sender.\n */\n private findMatchingHandler(sender: string): MessageHandler | undefined {\n for (const [filter, handler] of this.messageHandlers) {\n if (filter.matches(sender)) {\n return handler\n }\n }\n return undefined\n }\n\n /**\n * Handle incoming Kafka message.\n */\n private handleKafkaMessage(kafkaMsg: KafkaMessage): void {\n // Run handler asynchronously\n this.handleMessageAsync(kafkaMsg).catch((error) => {\n console.error('Error in message handler:', error)\n })\n }\n\n /**\n * Async message handling with error recovery.\n */\n private async handleMessageAsync(kafkaMsg: KafkaMessage): Promise<void> {\n let message: Message | undefined\n\n try {\n // Create message from Kafka data\n message = Message.fromKafkaMessage(\n kafkaMsg.body,\n kafkaMsg.headers,\n {\n conversationClient: this.conversationClient,\n memoryClient: this.memoryClient,\n memoryV2Client: this.memoryV2Client,\n attachmentClient: this.attachmentClient,\n agentName: this.name,\n gatewayClient: this.gatewayClient,\n }\n )\n\n // Find matching handler\n const sender = this.getSender(kafkaMsg.headers)\n const handler = this.findMatchingHandler(sender)\n\n if (!handler) {\n console.warn(`No handler matched for sender: ${sender}`)\n return\n }\n\n // Extract trace ID from Kafka headers for cross-agent Braintrust correlation\n const traceId = kafkaMsg.headers.traceId\n\n // Wrap handler execution in Sentry invoke_agent span\n // setConversationId must be called INSIDE the span so the conversation ID\n // is applied within the span's scope (see Sentry AI Agents docs)\n await withAgentSpan(\n `invoke_agent ${this.name}`,\n this.name,\n 'gen_ai.invoke_agent',\n async () => {\n setConversationId(message!.conversationId)\n\n // Also wrap in Braintrust span (nested under conversation trace)\n return withBraintrustSpan(\n `invoke_agent ${this.name}`,\n () => _messageContext.run(\n {\n userMessageId: message!.userMessageId,\n responseMessageId: message!.responseMessageId,\n },\n () => handler(message!, sender),\n ),\n {\n conversationId: message!.conversationId,\n agentName: this.name,\n traceId,\n type: 'llm',\n input: [{ role: 'user', content: message!.content }],\n metadata: {\n conversationId: message!.conversationId,\n agentName: this.name,\n sender,\n },\n },\n )\n },\n {\n 'gen_ai.operation.name': 'invoke_agent',\n 'gen_ai.conversation.id': message.conversationId,\n }\n )\n\n } catch (error) {\n console.error('Error handling message:', error)\n\n // Capture error in Sentry with agent context\n captureAgentError(error, this.name, {\n conversationId: message?.conversationId ?? 'unknown',\n })\n\n // Log error in Braintrust\n logBraintrustError(error, {\n conversationId: message?.conversationId ?? 'unknown',\n agentName: this.name,\n })\n\n // Send error response if we have a message\n if (message && this.sendErrorResponses) {\n await this.sendErrorResponse(message, error as Error)\n }\n }\n }\n\n /**\n * Send error response to user when handler fails.\n */\n private async sendErrorResponse(message: Message, _error: Error): Promise<void> {\n try {\n const streamer = this.streamer(message)\n streamer.stream(this.errorMessageTemplate)\n await streamer.finish()\n } catch (err) {\n console.error('Failed to send error response:', err)\n }\n }\n\n /**\n * Report an error with agent context.\n *\n * Logs to console.error and captures in Sentry (if configured) in one call.\n * Use this for errors that don't involve streaming a response (e.g. background tasks).\n * For errors during streaming, use `streamer.streamError()` instead.\n *\n * @param error - The error to report\n * @param context - Optional additional context tags\n *\n * @example\n * ```typescript\n * try {\n * await riskyOperation()\n * } catch (e) {\n * agent.reportError(e)\n * }\n * ```\n */\n reportError(error: unknown, context?: Record<string, string>): void {\n console.error(`[${this.name}] Error:`, error)\n captureAgentError(error, this.name, context)\n }\n\n /**\n * Create a new Streamer for streaming responses.\n */\n streamer(message: Message, options: StreamerOptions = {}): Streamer {\n return new Streamer({\n agentName: this.name,\n originalMessage: message,\n gatewayClient: this.gatewayClient,\n conversationClient: this.conversationClient,\n attachmentClient: this.attachmentClient,\n renderClient: this.renderClient,\n awaiting: options.awaiting ?? false,\n })\n }\n\n /**\n * Call another agent and wait for its response.\n *\n * Sends a message to the target agent with isCall/callId headers.\n * The response is intercepted by the app's call interception logic\n * and returned as a string.\n *\n * @param agentName - Target agent name to call\n * @param conversationId - The conversation ID for context\n * @param content - Content to send to the target agent\n * @param timeout - Timeout in milliseconds (default: 30000)\n * @returns The target agent's response as a Message (with content, attachments, metadata, etc.)\n *\n * @example\n * ```typescript\n * const response = await agent.call('medical-agent', message.conversationId, 'Is ibuprofen safe?')\n * streamer.stream(`Medical agent says: ${response.content}`)\n * // Access attachments:\n * if (response.hasAttachments()) {\n * const bytes = await response.getAttachmentBytes()\n * }\n * ```\n */\n async call(\n agentName: string,\n conversationId: string,\n content: string,\n timeout: number = 30000,\n ): Promise<Message> {\n if (!this._app) {\n throw new Error(\n 'agent.call() requires a BasionAgentApp. ' +\n 'Ensure the agent was created via app.registerMe().'\n )\n }\n\n return withAgentSpan(\n `handoff from ${this.name} to ${agentName}`,\n this.name,\n 'gen_ai.handoff',\n async () => {\n const callId = randomUUID()\n\n // Create a promise that will be resolved when the response arrives\n let resolvePromise!: (value: Message) => void\n let rejectPromise!: (reason: Error) => void\n const resultPromise = new Promise<Message>((resolve, reject) => {\n resolvePromise = resolve\n rejectPromise = reject\n })\n\n // Register the pending call with the app\n this._app!._pendingCalls.set(callId, {\n resolve: resolvePromise,\n reject: rejectPromise,\n callerName: this.name,\n })\n\n try {\n // Produce the call message\n const headers: Record<string, string> = {\n conversationId,\n from_: this.name,\n to_: agentName,\n nextRoute: agentName,\n isCall: 'true',\n callId,\n }\n\n const body: Record<string, unknown> = {\n content,\n done: true,\n persist: false,\n }\n\n this.gatewayClient.produce('router.inbox', conversationId, headers, body)\n\n // Wait for response with timeout\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error('TIMEOUT')), timeout)\n })\n\n const result = await Promise.race([resultPromise, timeoutPromise])\n return result\n } catch (error) {\n // Clean up on error/timeout\n this._app!._pendingCalls.delete(callId)\n this._app!._callContent.delete(callId)\n\n if (error instanceof Error && error.message === 'TIMEOUT') {\n throw new Error(`agent.call() to '${agentName}' timed out after ${timeout}ms`)\n }\n throw error\n }\n }\n ) as Promise<Message>\n }\n\n /**\n * Start a new conversation and send the first message as an agent.\n *\n * Creates a conversation in the conversation store and returns a Streamer\n * for sending the initial message. The message flows through the normal\n * Kafka pipeline (router → user.inbox → provider → Centrifugo).\n *\n * The user's reply will enter the normal handler pipeline via onMessage().\n *\n * @param options - Configuration for the new conversation\n * @returns Tuple of [conversationId, streamer]\n *\n * @example\n * ```typescript\n * const [convId, streamer] = await agent.startConversation({\n * userId: 'user-uuid',\n * title: 'Weekly Check-in',\n * })\n * streamer.stream('Hi! How are you feeling today?')\n * await streamer.finish()\n * ```\n */\n async startConversation(options: {\n userId: string\n title?: string\n awaiting?: boolean\n responseSchema?: Record<string, unknown>\n messageMetadata?: Record<string, unknown>\n metadata?: Record<string, unknown>\n }): Promise<[string, Streamer]> {\n if (!this.conversationClient) {\n throw new ConfigurationError(\n 'ConversationClient is required for startConversation. ' +\n 'Ensure the agent is created via BasionAgentApp.'\n )\n }\n\n // Create conversation with current_route and locked_by set atomically\n const conversation = await this.conversationClient.createConversation({\n userId: options.userId,\n title: options.title ?? 'Agent-initiated conversation',\n agentIdentifier: this.name,\n currentRoute: this.name,\n lockedBy: this.name,\n isNew: true,\n metadata: options.metadata,\n })\n\n const conversationId = String(conversation.id)\n\n // Create streamer with direct conversationId/userId (no originalMessage)\n const streamer = new Streamer({\n agentName: this.name,\n conversationId,\n userId: options.userId,\n gatewayClient: this.gatewayClient,\n conversationClient: this.conversationClient,\n attachmentClient: this.attachmentClient,\n renderClient: this.renderClient,\n awaiting: options.awaiting ?? false,\n })\n\n if (options.responseSchema) {\n streamer.setResponseSchema(options.responseSchema)\n }\n if (options.messageMetadata) {\n streamer.setMessageMetadata(options.messageMetadata)\n }\n\n return [conversationId, streamer]\n }\n\n /**\n * Mark agent as ready to consume messages.\n */\n startConsuming(): void {\n if (this.messageHandlers.length === 0) {\n throw new ConfigurationError('No message handlers registered')\n }\n\n if (!this.initialized) {\n throw new ConfigurationError('Agent not initialized with gateway')\n }\n\n this.running = true\n console.log(`Agent ${this.name} is now consuming messages`)\n }\n\n /**\n * Shutdown agent gracefully.\n */\n shutdown(): void {\n this.running = false\n\n if (this.heartbeatManager) {\n this.heartbeatManager.stop()\n }\n\n console.log(`Agent ${this.name} shutdown`)\n }\n\n /**\n * Check if agent is running.\n */\n isRunning(): boolean {\n return this.running\n }\n}\n","/**\n * Basion Agent SDK - Agent State Client\n * Port of Python basion_agent/agent_state_client.py\n */\n\nimport { APIException } from './exceptions.js'\n\nexport interface AgentStateResponse {\n conversationId: string\n namespace: string\n state: Record<string, unknown>\n createdAt?: string\n updatedAt?: string\n}\n\n/**\n * Async HTTP client for agent-state API.\n * Used for storing framework-specific state (e.g., Pydantic AI messages).\n */\nexport class AgentStateClient {\n private readonly baseUrl: string\n\n constructor(baseUrl: string) {\n this.baseUrl = baseUrl.replace(/\\/$/, '')\n }\n\n /**\n * Store or update agent state.\n */\n async put(\n conversationId: string,\n namespace: string,\n state: Record<string, unknown>\n ): Promise<{ conversationId: string; namespace: string; created: boolean }> {\n const url = `${this.baseUrl}/agent-state/${conversationId}`\n const payload = {\n namespace,\n state,\n }\n\n try {\n const response = await fetch(url, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to store agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversationId: string; namespace: string; created: boolean }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to store agent state: ${error}`)\n }\n }\n\n /**\n * Get agent state.\n */\n async get(\n conversationId: string,\n namespace: string\n ): Promise<AgentStateResponse | null> {\n const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`)\n url.searchParams.set('namespace', namespace)\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get agent state: ${error}`)\n }\n }\n\n /**\n * Delete agent state.\n */\n async delete(\n conversationId: string,\n namespace?: string\n ): Promise<{ conversationId: string; namespace?: string; deleted: number }> {\n const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`)\n if (namespace) {\n url.searchParams.set('namespace', namespace)\n }\n\n try {\n const response = await fetch(url.toString(), { method: 'DELETE' })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to delete agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversationId: string; namespace?: string; deleted: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to delete agent state: ${error}`)\n }\n }\n\n // ── Vercel AI Messages (row-per-message store) ──────────────────────\n\n /**\n * Append Vercel AI messages to a conversation.\n */\n async appendVercelMessages(\n conversationId: string,\n messages: Record<string, unknown>[],\n ): Promise<{ conversation_id: string; appended: number; total: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}/append`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ messages }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to append vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; appended: number; total: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to append vercel messages: ${error}`)\n }\n }\n\n /**\n * Get Vercel AI messages for a conversation.\n * Returns AgentStateResponse-compatible shape or null if not found.\n */\n async getVercelMessages(\n conversationId: string,\n last?: number,\n ): Promise<AgentStateResponse | null> {\n const url = new URL(`${this.baseUrl}/vercel-ai-messages/${conversationId}`)\n if (last !== undefined) {\n url.searchParams.set('last', String(last))\n }\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get vercel messages: ${error}`)\n }\n }\n\n /**\n * Delete all Vercel AI messages for a conversation.\n */\n async deleteVercelMessages(\n conversationId: string,\n ): Promise<{ conversation_id: string; deleted: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}`\n\n try {\n const response = await fetch(url, { method: 'DELETE' })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to delete vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; deleted: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to delete vercel messages: ${error}`)\n }\n }\n\n // ── Filesystem Snapshots (row-per-snapshot store) ─────────────────────\n\n /**\n * Append filesystem snapshots to a conversation.\n */\n async appendFilesystemSnapshots(\n conversationId: string,\n snapshots: Record<string, unknown>[],\n ): Promise<{ conversation_id: string; appended: number; total: number }> {\n const url = `${this.baseUrl}/filesystem-snapshots/${conversationId}/append`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ snapshots }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to append filesystem snapshots: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; appended: number; total: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to append filesystem snapshots: ${error}`)\n }\n }\n\n /**\n * Get filesystem snapshots for a conversation.\n * Returns AgentStateResponse-compatible shape or null if not found.\n */\n async getFilesystemSnapshots(\n conversationId: string,\n ): Promise<AgentStateResponse | null> {\n const url = `${this.baseUrl}/filesystem-snapshots/${conversationId}`\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get filesystem snapshots: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get filesystem snapshots: ${error}`)\n }\n }\n}\n","/**\n * Basion Agent SDK - Vercel AI SDK Extension\n *\n * VercelAIMessageStore for persistent message history with Vercel AI SDK.\n *\n * @packageDocumentation\n */\n\nimport type { BasionAgentApp } from '../app.js'\nimport { _messageContext } from '../agent.js'\nimport { AgentStateClient } from '../agent-state-client.js'\n\n// Type definitions compatible with Vercel AI SDK's CoreMessage\ninterface CoreMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content: string | Array<{ type: string;[key: string]: unknown }>\n [key: string]: unknown\n}\n\n/**\n * Persistent message store for Vercel AI SDK.\n *\n * Stores and retrieves AI SDK message history using the agent-state API.\n * Cross-system message IDs (`_messageId`) are embedded automatically — no\n * manual ID passing required.\n *\n * @example\n * ```typescript\n * import { BasionAgentApp } from 'basion-ai-sdk'\n * import { VercelAIMessageStore } from 'basion-ai-sdk/extensions/vercel-ai'\n * import { streamText } from 'ai'\n * import { anthropic } from '@ai-sdk/anthropic'\n *\n * const app = new BasionAgentApp({ gatewayUrl: 'agent-gateway:8080', apiKey: 'your-key' })\n * const store = new VercelAIMessageStore(app)\n * const agent = await app.registerMe({ ... })\n *\n * agent.onMessage(async (message) => {\n * const history = await store.load(message.conversationId)\n *\n * const messages = [...history, { role: 'user', content: message.content }]\n *\n * const result = await streamText({ model: anthropic('claude-sonnet-4-6'), messages })\n *\n * // _messageId is embedded automatically — no IDs to pass\n * await store.append(message.conversationId, [\n * { role: 'user', content: message.content },\n * ...result.response.messages,\n * ])\n * })\n *\n * app.run()\n * ```\n */\nexport class VercelAIMessageStore {\n private static readonly NAMESPACE = 'vercel_ai'\n private readonly client: AgentStateClient\n\n constructor(app: BasionAgentApp) {\n this.client = new AgentStateClient(app.gatewayClient.conversationStoreUrl)\n }\n\n /**\n * Load message history for a conversation.\n *\n * @param conversationId - The conversation ID to load history for\n * @returns Array of CoreMessage objects (empty array if no history)\n */\n async load(conversationId: string): Promise<CoreMessage[]> {\n const result = await this.client.getVercelMessages(conversationId)\n\n if (!result) {\n return []\n }\n\n const state = result.state ?? {}\n const messages = state.messages as unknown[]\n\n if (!Array.isArray(messages)) {\n return []\n }\n\n return messages.filter(this.isValidMessage) as CoreMessage[]\n }\n\n /**\n * Append new messages to existing history.\n *\n * Cross-system `_messageId` fields are embedded automatically from the\n * current message handler context — no IDs need to be passed manually.\n *\n * @param conversationId - The conversation ID\n * @param newMessages - New messages to append (user turn + response messages)\n */\n async append(conversationId: string, newMessages: CoreMessage[]): Promise<void> {\n const ctx = _messageContext.getStore()\n const tagged = ctx ? this.embedMessageIds(newMessages, ctx) : newMessages\n await this.client.appendVercelMessages(\n conversationId,\n tagged.map(this.serializeMessage),\n )\n }\n\n /**\n * Clear message history for a conversation.\n *\n * @param conversationId - The conversation ID to clear history for\n */\n async clear(conversationId: string): Promise<void> {\n await this.client.deleteVercelMessages(conversationId)\n }\n\n /**\n * Get the last N messages from history.\n *\n * @param conversationId - The conversation ID\n * @param count - Number of messages to retrieve\n */\n async getLastMessages(conversationId: string, count: number): Promise<CoreMessage[]> {\n const result = await this.client.getVercelMessages(conversationId, count)\n\n if (!result) {\n return []\n }\n\n const state = result.state ?? {}\n const messages = state.messages as unknown[]\n\n if (!Array.isArray(messages)) {\n return []\n }\n\n return messages.filter(this.isValidMessage) as CoreMessage[]\n }\n\n private embedMessageIds(\n messages: CoreMessage[],\n ids: { userMessageId?: string; responseMessageId?: string },\n ): CoreMessage[] {\n const result = [...messages]\n\n if (ids.userMessageId) {\n const userIdx = result.findIndex((m) => m.role === 'user')\n if (userIdx !== -1) {\n result[userIdx] = { ...result[userIdx], _messageId: ids.userMessageId }\n }\n }\n\n if (ids.responseMessageId) {\n for (let i = result.length - 1; i >= 0; i--) {\n const m = result[i]\n if (\n m.role === 'assistant' &&\n (typeof m.content === 'string'\n ? m.content.length > 0\n : (m.content as Array<{ type: string }>).some((c) => c.type === 'text'))\n ) {\n result[i] = { ...result[i], _messageId: ids.responseMessageId }\n break\n }\n }\n }\n\n return result\n }\n\n private isValidMessage(msg: unknown): msg is CoreMessage {\n if (typeof msg !== 'object' || msg === null) return false\n const m = msg as Record<string, unknown>\n return (\n typeof m.role === 'string' &&\n ['system', 'user', 'assistant', 'tool'].includes(m.role) &&\n (typeof m.content === 'string' || Array.isArray(m.content))\n )\n }\n\n private serializeMessage(msg: CoreMessage): Record<string, unknown> {\n return { ...msg }\n }\n}\n\nexport type { CoreMessage }\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/exceptions.ts","../../src/types.ts","../../src/agent.ts","../../src/agent-state-client.ts","../../src/extensions/vercel-ai.ts"],"names":[],"mappings":"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","file":"vercel-ai.js","sourcesContent":["/**\n * Basion Agent SDK - Custom Errors\n * Port of Python basion_agent/exceptions.py\n */\n\n/**\n * Base error class for Basion Agent SDK\n */\nexport class BasionAgentError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'BasionAgentError'\n Error.captureStackTrace?.(this, this.constructor)\n }\n}\n\n/**\n * Error during agent registration with AI Inventory\n */\nexport class RegistrationError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'RegistrationError'\n }\n}\n\n/**\n * Error during Kafka operations (produce/consume)\n */\nexport class KafkaError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'KafkaError'\n }\n}\n\n/**\n * Error in SDK configuration\n */\nexport class ConfigurationError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigurationError'\n }\n}\n\n/**\n * Error from API calls to backend services\n */\nexport class APIException extends BasionAgentError {\n statusCode?: number\n\n constructor(message: string, statusCode?: number) {\n super(message)\n this.name = 'APIException'\n this.statusCode = statusCode\n }\n}\n\n/**\n * Error during heartbeat operations\n */\nexport class HeartbeatError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'HeartbeatError'\n }\n}\n\n/**\n * Error during gRPC connection/communication\n */\nexport class GatewayError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'GatewayError'\n }\n}\n\n/**\n * Error during reconnection attempts\n */\nexport class ReconnectionError extends BasionAgentError {\n /** Number of attempts made before giving up */\n attempts: number\n /** The last error that occurred during reconnection */\n lastError?: Error\n\n constructor(message: string, attempts: number, lastError?: Error) {\n super(message)\n this.name = 'ReconnectionError'\n this.attempts = attempts\n this.lastError = lastError\n }\n}\n","/**\n * Basion Agent SDK - Shared Types\n * Port of Python basion_agent types\n */\n\nimport { z } from 'zod'\n\n// ============================================================================\n// Kafka Message Types\n// ============================================================================\n\nexport interface KafkaMessage {\n topic: string\n partition: number\n offset: number\n key: string\n headers: Record<string, string>\n body: Record<string, unknown>\n timestamp: number\n}\n\nexport interface KafkaHeaders {\n conversationId?: string\n userId?: string\n from_?: string\n to_?: string\n messageMetadata?: string\n messageSchema?: string\n /** @deprecated Use attachments (plural) instead. Kept for backward compatibility. */\n attachment?: string\n /** JSON-stringified array of AttachmentInfo objects */\n attachments?: string\n /** UUID of the user message in conversation-store (for cross-system mapping) */\n userMessageId?: string\n /** Pre-generated UUID for the assistant response in conversation-store (for cross-system mapping) */\n assistantMessageId?: string\n [key: string]: string | undefined\n}\n\n// ============================================================================\n// Agent Registration Types\n// ============================================================================\n\nexport interface AgentRegistrationData {\n id: string\n name: string\n about: string\n document: string\n representationName?: string\n baseUrl?: string\n metadata?: Record<string, unknown>\n relatedPages?: RelatedPage[]\n}\n\nexport interface RelatedPage {\n name: string\n endpoint: string\n}\n\nexport interface Prompt {\n label: string\n prompt: string\n}\n\nexport interface RegisterOptions {\n name: string\n about: string\n document: string\n representationName?: string\n baseUrl?: string\n metadata?: Record<string, unknown>\n relatedPages?: RelatedPage[]\n categoryNames?: string[]\n prompts?: Prompt[]\n detailedDescription?: string\n version?: string\n lifecycleStage?: 'dev' | 'test' | 'private-preview' | 'public-preview' | 'public'\n welcomeMessage?: string\n waitlist?: boolean\n forceUpdate?: boolean\n}\n\n// ============================================================================\n// Message Types\n// ============================================================================\n\n// Forward reference - actual Message class is imported where needed\n// Using 'any' here to avoid circular dependency, but consumers should use Message type\nexport interface MessageHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (message: any, sender: string): void | Promise<void>\n}\n\nexport interface SenderFilterOptions {\n senders?: string[]\n}\n\n// ============================================================================\n// Streamer Types\n// ============================================================================\n\nexport interface StreamOptions {\n persist?: boolean\n eventType?: string\n}\n\nexport interface StreamerOptions {\n awaiting?: boolean\n}\n\n// ============================================================================\n// Attachment Types\n// ============================================================================\n\nexport const AttachmentInfoSchema = z.object({\n url: z.string(),\n filename: z.string(),\n contentType: z.string(),\n size: z.number().optional(),\n fileType: z.string().optional(),\n objectKey: z.string().optional(),\n})\n\nexport type AttachmentInfo = z.infer<typeof AttachmentInfoSchema>\n\nexport function isImageAttachment(attachment: AttachmentInfo): boolean {\n return attachment.contentType?.startsWith('image/') || false\n}\n\nexport function isPdfAttachment(attachment: AttachmentInfo): boolean {\n return attachment.contentType === 'application/pdf'\n}\n\n// ============================================================================\n// Conversation Message Types\n// ============================================================================\n\nexport interface ConversationMessageData {\n id: string\n conversationId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n from?: string\n to?: string\n createdAt: string\n metadata?: Record<string, unknown>\n}\n\n// ============================================================================\n// Gateway Client Types\n// ============================================================================\n\nexport interface ProduceAck {\n topic: string\n partition: number\n offset: number\n correlationId?: string\n}\n\n// ============================================================================\n// Reconnection Types\n// ============================================================================\n\n/**\n * Configuration options for automatic reconnection behavior.\n */\nexport interface ReconnectionOptions {\n /** Maximum number of reconnection attempts before giving up. Default: 10 */\n maxRetries?: number\n /** Initial delay in milliseconds before first retry. Default: 1000 */\n initialDelayMs?: number\n /** Maximum delay in milliseconds between retries. Default: 30000 */\n maxDelayMs?: number\n /** Multiplier for exponential backoff. Default: 2 */\n backoffMultiplier?: number\n /** Whether to automatically reconnect on disconnect. Default: true */\n autoReconnect?: boolean\n}\n\n/**\n * Connection state for the gateway client.\n */\nexport type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'\n\n/**\n * Listener callback for connection state changes.\n */\nexport interface ConnectionStateListener {\n (state: ConnectionState, error?: Error): void\n}\n\n/**\n * Default reconnection options.\n */\nexport const DEFAULT_RECONNECTION_OPTIONS: Required<ReconnectionOptions> = {\n maxRetries: 10,\n initialDelayMs: 1000,\n maxDelayMs: 30000,\n backoffMultiplier: 2,\n autoReconnect: true,\n}\n\n// ============================================================================\n// App Configuration Types\n// ============================================================================\n\nexport interface AppOptions {\n gatewayUrl: string\n apiKey: string\n heartbeatInterval?: number\n maxConcurrentTasks?: number\n errorMessageTemplate?: string\n secure?: boolean\n /** Reconnection options for the gateway connection */\n reconnection?: ReconnectionOptions\n /** Enable remote logging to Loki via the gateway (default: false) */\n enableRemoteLogging?: boolean\n /** Minimum log level for remote logging (default: 'info') */\n remoteLogLevel?: 'debug' | 'info' | 'warn' | 'error'\n /** Number of logs to batch before sending (default: 100) */\n remoteLogBatchSize?: number\n /** Maximum seconds between log flushes (default: 5.0) */\n remoteLogFlushInterval?: number\n /** Sentry configuration (optional — omit or leave dsn empty to disable) */\n sentry?: import('./sentry.js').SentryConfig\n /** Braintrust configuration (optional — omit or leave apiKey empty to disable) */\n braintrust?: import('./braintrust.js').BraintrustConfig\n}\n\n// ============================================================================\n// HTTP Response Types\n// ============================================================================\n\nexport interface ConversationData {\n id: string\n userId: string\n agentId?: string\n awaitingRoute?: string\n pendingResponseSchema?: Record<string, unknown>\n createdAt: string\n updatedAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface GetMessagesOptions {\n role?: string\n from?: string\n to?: string\n limit?: number\n offset?: number\n}\n\n// ============================================================================\n// Memory Types\n// ============================================================================\n\n/**\n * A message stored in ai-memory.\n * Matches the ai-memory API MessageStored schema.\n */\nexport interface MemoryMessage {\n id?: string\n role: 'user' | 'assistant' | 'system'\n content: string\n tenantId?: string\n userId?: string\n conversationId?: string\n metadata?: Record<string, unknown>\n timestamp?: string\n sequenceNumber?: number\n}\n\n/**\n * Options for memory search operations.\n */\nexport interface MemorySearchOptions {\n /** Maximum number of results (1-100, default: 10) */\n limit?: number\n /** Minimum similarity threshold (0-100, default: 70) */\n minSimilarity?: number\n /** Number of surrounding context messages to include (0-20, default: 0) */\n contextMessages?: number\n /** Tenant ID for multi-tenant filtering */\n tenantId?: string\n /** Tenant matching mode: 'exact' or 'hierarchy' */\n tenantMatch?: 'exact' | 'hierarchy'\n}\n\n/**\n * A memory search result with similarity score and optional context.\n */\nexport interface MemorySearchResult {\n message: MemoryMessage\n score: number\n context: MemoryMessage[]\n}\n\n/**\n * User conversation summary from ai-memory.\n */\nexport interface UserSummary {\n text: string\n lastUpdated: string\n messageCount: number\n version: number\n}\n","/**\n * Basion Agent SDK - Agent\n * Port of Python basion_agent/agent.py\n */\n\nimport { randomUUID } from 'crypto'\nimport { AsyncLocalStorage } from 'node:async_hooks'\n\nimport type { BasionAgentApp } from './app.js'\nimport type { AttachmentClient } from './attachment-client.js'\nimport { logBraintrustError,withBraintrustSpan } from './braintrust.js'\nimport type { ConversationClient } from './conversation-client.js'\nimport { ConfigurationError } from './exceptions.js'\nimport type { GatewayClient } from './gateway-client.js'\nimport { type HeartbeatFailureCallback,HeartbeatManager } from './heartbeat.js'\nimport type { MemoryClient } from './memory-client.js'\nimport type { MemoryV2Client } from './memory-v2-client.js'\nimport { Message } from './message.js'\nimport type { RenderClient } from './render-client.js'\nimport { captureAgentError, setConversationId,withAgentSpan } from './sentry.js'\nimport { Streamer } from './streamer.js'\nimport { Tools } from './tools/container.js'\nimport type {\n AgentRegistrationData,\n KafkaMessage,\n MessageHandler,\n SenderFilterOptions,\n StreamerOptions,\n} from './types.js'\n\n/**\n * AsyncLocalStorage context set automatically by the agent message handler.\n * VercelAIMessageStore reads from this to embed cross-system IDs without\n * requiring agent developers to pass them explicitly.\n *\n * Stored on globalThis so that the same instance is shared across all bundle\n * chunks (tsup can include this module in multiple output files; globalThis\n * ensures they all reference the same AsyncLocalStorage object).\n *\n * @internal — consumed by VercelAIMessageStore; not part of the public API.\n */\nconst _msgCtxKey = Symbol.for('basion-ai-sdk.messageContext')\nif (!(globalThis as Record<symbol, unknown>)[_msgCtxKey]) {\n (globalThis as Record<symbol, unknown>)[_msgCtxKey] = new AsyncLocalStorage<{\n userMessageId?: string\n responseMessageId?: string\n }>()\n}\nexport const _messageContext = (globalThis as Record<symbol, unknown>)[_msgCtxKey] as AsyncLocalStorage<{\n userMessageId?: string\n responseMessageId?: string\n}>\n\n/**\n * Filter for matching message senders.\n */\nexport class SenderFilter {\n private readonly _include: Set<string> = new Set()\n private readonly _exclude: Set<string> = new Set()\n readonly matchAll: boolean\n\n constructor(senders?: string[]) {\n if (!senders || senders.length === 0) {\n this.matchAll = true\n } else {\n this.matchAll = false\n for (const sender of senders) {\n if (sender.startsWith('~')) {\n this._exclude.add(sender.slice(1))\n } else {\n this._include.add(sender)\n }\n }\n }\n }\n\n /**\n * Get included senders as an array (for compatibility with Python tests).\n */\n get include(): string[] {\n return Array.from(this._include)\n }\n\n /**\n * Get excluded senders as an array (for compatibility with Python tests).\n */\n get exclude(): string[] {\n return Array.from(this._exclude)\n }\n\n /**\n * Check if sender matches this filter.\n */\n matches(sender: string): boolean {\n // If excluded, reject\n if (this._exclude.has(sender)) {\n return false\n }\n // If match all or explicitly included\n if (this.matchAll || this._include.size === 0) {\n return true\n }\n return this._include.has(sender)\n }\n}\n\n// Message handlers stored as tuples: [SenderFilter, MessageHandler]\ntype RegisteredHandler = [SenderFilter, MessageHandler]\n\n/**\n * Core agent class that handles messaging via the Agent Gateway.\n */\nexport class Agent {\n readonly name: string\n readonly agentData: AgentRegistrationData\n sendErrorResponses: boolean = true\n errorMessageTemplate: string\n\n private readonly gatewayClient: GatewayClient\n private readonly conversationClient?: ConversationClient\n private readonly memoryClient?: MemoryClient\n private readonly memoryV2Client?: MemoryV2Client\n private readonly attachmentClient?: AttachmentClient\n private readonly renderClient?: RenderClient\n private readonly heartbeatInterval: number\n private readonly heartbeatFailureThreshold: number\n private readonly onHeartbeatFailure?: HeartbeatFailureCallback\n\n /** Set by BasionAgentApp.registerMe() to enable agent.call() */\n _app?: BasionAgentApp\n\n private messageHandlers: RegisteredHandler[] = []\n private heartbeatManager?: HeartbeatManager\n private initialized: boolean = false\n private running: boolean = false\n private _tools?: Tools\n\n /** AbortControllers for cancellable conversations, keyed by conversationId */\n private _cancelControllers = new Map<string, AbortController>()\n /** Tracks cancel requests that arrived before the handler created an AbortController */\n private _cancelledConversations = new Set<string>()\n\n constructor(options: {\n name: string\n gatewayClient: GatewayClient\n agentData: AgentRegistrationData\n heartbeatInterval?: number\n heartbeatFailureThreshold?: number\n onHeartbeatFailure?: HeartbeatFailureCallback\n conversationClient?: ConversationClient\n memoryClient?: MemoryClient\n memoryV2Client?: MemoryV2Client\n attachmentClient?: AttachmentClient\n renderClient?: RenderClient\n errorMessageTemplate?: string\n }) {\n this.name = options.name\n this.gatewayClient = options.gatewayClient\n this.agentData = options.agentData\n this.heartbeatInterval = options.heartbeatInterval ?? 60\n this.heartbeatFailureThreshold = options.heartbeatFailureThreshold ?? 3\n this.onHeartbeatFailure = options.onHeartbeatFailure\n this.conversationClient = options.conversationClient\n this.memoryClient = options.memoryClient\n this.memoryV2Client = options.memoryV2Client\n this.attachmentClient = options.attachmentClient\n this.renderClient = options.renderClient\n this.errorMessageTemplate = options.errorMessageTemplate ??\n 'I encountered an error while processing your message. Please try again or contact support if the issue persists.'\n }\n\n /**\n * Create an AbortSignal for a conversation's current operation.\n * Call this before starting work (e.g. streamText) and pass the\n * returned signal so the operation can be cancelled externally.\n *\n * If a cancel request arrived before this call, the returned signal\n * will already be in the aborted state.\n */\n createAbortSignal(conversationId: string): AbortSignal {\n // Replace any existing controller (defensive cleanup)\n const existing = this._cancelControllers.get(conversationId)\n if (existing) {\n existing.abort()\n }\n\n const controller = new AbortController()\n this._cancelControllers.set(conversationId, controller)\n\n // Handle race: cancel arrived before handler created the controller\n if (this._cancelledConversations.has(conversationId)) {\n this._cancelledConversations.delete(conversationId)\n controller.abort()\n }\n\n return controller.signal\n }\n\n /**\n * Cancel the running operation for a conversation.\n * Called by the message interceptor when a cancel message arrives.\n */\n cancelConversation(conversationId: string): void {\n const controller = this._cancelControllers.get(conversationId)\n if (controller) {\n controller.abort()\n this._cancelControllers.delete(conversationId)\n } else {\n // Cancel arrived before handler started — mark for pre-abort\n this._cancelledConversations.add(conversationId)\n }\n }\n\n /**\n * Clean up the abort controller after the handler completes.\n */\n clearAbortController(conversationId: string): void {\n this._cancelControllers.delete(conversationId)\n this._cancelledConversations.delete(conversationId)\n }\n\n /**\n * Access to tools (knowledge graph, etc.).\n *\n * Example:\n * const kg = agent.tools.knowledgeGraph\n * const diseases = await kg.searchDiseases({ name: \"Huntington\" })\n */\n get tools(): Tools {\n if (!this._tools) {\n this._tools = new Tools(this.gatewayClient)\n }\n return this._tools\n }\n\n /**\n * Register a message handler.\n * \n * Usage:\n * agent.onMessage(async (message, sender) => { ... })\n * agent.onMessage(handler, { senders: ['user'] })\n */\n onMessage(handler: MessageHandler): void\n onMessage(handler: MessageHandler, options: SenderFilterOptions): void\n onMessage(\n handler: MessageHandler,\n options?: SenderFilterOptions\n ): void {\n const filter = new SenderFilter(options?.senders)\n this.messageHandlers.push([filter, handler])\n }\n\n /**\n * Initialize agent after gateway connection is established.\n * Can be called multiple times (e.g., after reconnection) to re-register handlers.\n */\n initializeWithGateway(): void {\n // Register message handler with gateway\n // This is idempotent - calling it again just updates the handler\n const topic = `${this.name}.inbox`\n this.gatewayClient.registerHandler(topic, (msg) => {\n this.handleKafkaMessage(msg)\n })\n\n // Start heartbeat if not already created\n if (!this.heartbeatManager) {\n this.heartbeatManager = new HeartbeatManager(\n this.gatewayClient,\n this.name,\n this.heartbeatInterval,\n {\n failureThreshold: this.heartbeatFailureThreshold,\n onFailureThresholdExceeded: this.handleHeartbeatFailure.bind(this),\n }\n )\n }\n\n // Start or resume heartbeat (in case it was stopped during disconnect)\n if (!this.heartbeatManager.isRunning()) {\n this.heartbeatManager.start()\n } else if (this.heartbeatManager.isPaused()) {\n this.heartbeatManager.resume()\n }\n\n this.initialized = true\n }\n\n /**\n * Handle heartbeat failure threshold exceeded.\n * This can indicate a stale connection that needs reconnection.\n */\n private handleHeartbeatFailure(consecutiveFailures: number, lastError?: Error): void {\n console.warn(\n `Agent '${this.name}' heartbeat failure threshold exceeded (${consecutiveFailures} failures)`\n )\n\n // Call custom callback if provided\n if (this.onHeartbeatFailure) {\n this.onHeartbeatFailure(consecutiveFailures, lastError)\n }\n\n // If gateway is not connected, try to trigger reconnection\n if (!this.gatewayClient.isConnected() && !this.gatewayClient.isReconnectingNow()) {\n console.warn('Gateway not connected, attempting force reconnect...')\n this.gatewayClient.forceReconnect().catch((err) => {\n console.error('Force reconnect from heartbeat failure failed:', err)\n })\n }\n }\n\n /**\n * Pause heartbeat sending (e.g., during reconnection).\n */\n pauseHeartbeat(): void {\n this.heartbeatManager?.pause()\n }\n\n /**\n * Resume heartbeat sending.\n */\n resumeHeartbeat(): void {\n this.heartbeatManager?.resume()\n }\n\n /**\n * Get sender from message headers.\n */\n private getSender(headers: Record<string, string>): string {\n return headers.from_ ?? headers.from ?? 'user'\n }\n\n /**\n * Find first handler that matches the sender.\n */\n private findMatchingHandler(sender: string): MessageHandler | undefined {\n for (const [filter, handler] of this.messageHandlers) {\n if (filter.matches(sender)) {\n return handler\n }\n }\n return undefined\n }\n\n /**\n * Handle incoming Kafka message.\n */\n private handleKafkaMessage(kafkaMsg: KafkaMessage): void {\n // Run handler asynchronously\n this.handleMessageAsync(kafkaMsg).catch((error) => {\n console.error('Error in message handler:', error)\n })\n }\n\n /**\n * Async message handling with error recovery.\n */\n private async handleMessageAsync(kafkaMsg: KafkaMessage): Promise<void> {\n let message: Message | undefined\n\n try {\n // Create message from Kafka data\n message = Message.fromKafkaMessage(\n kafkaMsg.body,\n kafkaMsg.headers,\n {\n conversationClient: this.conversationClient,\n memoryClient: this.memoryClient,\n memoryV2Client: this.memoryV2Client,\n attachmentClient: this.attachmentClient,\n agentName: this.name,\n gatewayClient: this.gatewayClient,\n }\n )\n\n // Find matching handler\n const sender = this.getSender(kafkaMsg.headers)\n const handler = this.findMatchingHandler(sender)\n\n if (!handler) {\n console.warn(`No handler matched for sender: ${sender}`)\n return\n }\n\n // Extract trace ID from Kafka headers for cross-agent Braintrust correlation\n const traceId = kafkaMsg.headers.traceId\n\n // Wrap handler execution in Sentry invoke_agent span\n // setConversationId must be called INSIDE the span so the conversation ID\n // is applied within the span's scope (see Sentry AI Agents docs)\n await withAgentSpan(\n `invoke_agent ${this.name}`,\n this.name,\n 'gen_ai.invoke_agent',\n async () => {\n setConversationId(message!.conversationId)\n\n // Also wrap in Braintrust span (nested under conversation trace)\n return withBraintrustSpan(\n `invoke_agent ${this.name}`,\n () => _messageContext.run(\n {\n userMessageId: message!.userMessageId,\n responseMessageId: message!.responseMessageId,\n },\n () => handler(message!, sender),\n ),\n {\n conversationId: message!.conversationId,\n agentName: this.name,\n traceId,\n type: 'llm',\n input: [{ role: 'user', content: message!.content }],\n metadata: {\n conversationId: message!.conversationId,\n agentName: this.name,\n sender,\n },\n },\n )\n },\n {\n 'gen_ai.operation.name': 'invoke_agent',\n 'gen_ai.conversation.id': message.conversationId,\n }\n )\n\n } catch (error) {\n console.error('Error handling message:', error)\n\n // Capture error in Sentry with agent context\n captureAgentError(error, this.name, {\n conversationId: message?.conversationId ?? 'unknown',\n })\n\n // Log error in Braintrust\n logBraintrustError(error, {\n conversationId: message?.conversationId ?? 'unknown',\n agentName: this.name,\n })\n\n // Send error response if we have a message\n if (message && this.sendErrorResponses) {\n await this.sendErrorResponse(message, error as Error)\n }\n }\n }\n\n /**\n * Send error response to user when handler fails.\n */\n private async sendErrorResponse(message: Message, _error: Error): Promise<void> {\n try {\n const streamer = this.streamer(message)\n streamer.stream(this.errorMessageTemplate)\n await streamer.finish()\n } catch (err) {\n console.error('Failed to send error response:', err)\n }\n }\n\n /**\n * Report an error with agent context.\n *\n * Logs to console.error and captures in Sentry (if configured) in one call.\n * Use this for errors that don't involve streaming a response (e.g. background tasks).\n * For errors during streaming, use `streamer.streamError()` instead.\n *\n * @param error - The error to report\n * @param context - Optional additional context tags\n *\n * @example\n * ```typescript\n * try {\n * await riskyOperation()\n * } catch (e) {\n * agent.reportError(e)\n * }\n * ```\n */\n reportError(error: unknown, context?: Record<string, string>): void {\n console.error(`[${this.name}] Error:`, error)\n captureAgentError(error, this.name, context)\n }\n\n /**\n * Create a new Streamer for streaming responses.\n */\n streamer(message: Message, options: StreamerOptions = {}): Streamer {\n return new Streamer({\n agentName: this.name,\n originalMessage: message,\n gatewayClient: this.gatewayClient,\n conversationClient: this.conversationClient,\n attachmentClient: this.attachmentClient,\n renderClient: this.renderClient,\n awaiting: options.awaiting ?? false,\n })\n }\n\n /**\n * Call another agent and wait for its response.\n *\n * Sends a message to the target agent with isCall/callId headers.\n * The response is intercepted by the app's call interception logic\n * and returned as a string.\n *\n * @param agentName - Target agent name to call\n * @param conversationId - The conversation ID for context\n * @param content - Content to send to the target agent\n * @param timeout - Timeout in milliseconds (default: 30000)\n * @returns The target agent's response as a Message (with content, attachments, metadata, etc.)\n *\n * @example\n * ```typescript\n * const response = await agent.call('medical-agent', message.conversationId, 'Is ibuprofen safe?')\n * streamer.stream(`Medical agent says: ${response.content}`)\n * // Access attachments:\n * if (response.hasAttachments()) {\n * const bytes = await response.getAttachmentBytes()\n * }\n * ```\n */\n async call(\n agentName: string,\n conversationId: string,\n content: string,\n timeout: number = 30000,\n ): Promise<Message> {\n if (!this._app) {\n throw new Error(\n 'agent.call() requires a BasionAgentApp. ' +\n 'Ensure the agent was created via app.registerMe().'\n )\n }\n\n return withAgentSpan(\n `handoff from ${this.name} to ${agentName}`,\n this.name,\n 'gen_ai.handoff',\n async () => {\n const callId = randomUUID()\n\n // Create a promise that will be resolved when the response arrives\n let resolvePromise!: (value: Message) => void\n let rejectPromise!: (reason: Error) => void\n const resultPromise = new Promise<Message>((resolve, reject) => {\n resolvePromise = resolve\n rejectPromise = reject\n })\n\n // Register the pending call with the app\n this._app!._pendingCalls.set(callId, {\n resolve: resolvePromise,\n reject: rejectPromise,\n callerName: this.name,\n })\n\n try {\n // Produce the call message\n const headers: Record<string, string> = {\n conversationId,\n from_: this.name,\n to_: agentName,\n nextRoute: agentName,\n isCall: 'true',\n callId,\n }\n\n const body: Record<string, unknown> = {\n content,\n done: true,\n persist: false,\n }\n\n this.gatewayClient.produce('router.inbox', conversationId, headers, body)\n\n // Wait for response with timeout\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error('TIMEOUT')), timeout)\n })\n\n const result = await Promise.race([resultPromise, timeoutPromise])\n return result\n } catch (error) {\n // Clean up on error/timeout\n this._app!._pendingCalls.delete(callId)\n this._app!._callContent.delete(callId)\n\n if (error instanceof Error && error.message === 'TIMEOUT') {\n throw new Error(`agent.call() to '${agentName}' timed out after ${timeout}ms`)\n }\n throw error\n }\n }\n ) as Promise<Message>\n }\n\n /**\n * Start a new conversation and send the first message as an agent.\n *\n * Creates a conversation in the conversation store and returns a Streamer\n * for sending the initial message. The message flows through the normal\n * Kafka pipeline (router → user.inbox → provider → Centrifugo).\n *\n * The user's reply will enter the normal handler pipeline via onMessage().\n *\n * @param options - Configuration for the new conversation\n * @returns Tuple of [conversationId, streamer]\n *\n * @example\n * ```typescript\n * const [convId, streamer] = await agent.startConversation({\n * userId: 'user-uuid',\n * title: 'Weekly Check-in',\n * })\n * streamer.stream('Hi! How are you feeling today?')\n * await streamer.finish()\n * ```\n */\n async startConversation(options: {\n userId: string\n title?: string\n awaiting?: boolean\n responseSchema?: Record<string, unknown>\n messageMetadata?: Record<string, unknown>\n metadata?: Record<string, unknown>\n }): Promise<[string, Streamer]> {\n if (!this.conversationClient) {\n throw new ConfigurationError(\n 'ConversationClient is required for startConversation. ' +\n 'Ensure the agent is created via BasionAgentApp.'\n )\n }\n\n // Create conversation with current_route and locked_by set atomically\n const conversation = await this.conversationClient.createConversation({\n userId: options.userId,\n title: options.title ?? 'Agent-initiated conversation',\n agentIdentifier: this.name,\n currentRoute: this.name,\n lockedBy: this.name,\n isNew: true,\n metadata: options.metadata,\n })\n\n const conversationId = String(conversation.id)\n\n // Create streamer with direct conversationId/userId (no originalMessage)\n const streamer = new Streamer({\n agentName: this.name,\n conversationId,\n userId: options.userId,\n gatewayClient: this.gatewayClient,\n conversationClient: this.conversationClient,\n attachmentClient: this.attachmentClient,\n renderClient: this.renderClient,\n awaiting: options.awaiting ?? false,\n })\n\n if (options.responseSchema) {\n streamer.setResponseSchema(options.responseSchema)\n }\n if (options.messageMetadata) {\n streamer.setMessageMetadata(options.messageMetadata)\n }\n\n return [conversationId, streamer]\n }\n\n /**\n * Mark agent as ready to consume messages.\n */\n startConsuming(): void {\n if (this.messageHandlers.length === 0) {\n throw new ConfigurationError('No message handlers registered')\n }\n\n if (!this.initialized) {\n throw new ConfigurationError('Agent not initialized with gateway')\n }\n\n this.running = true\n console.log(`Agent ${this.name} is now consuming messages`)\n }\n\n /**\n * Shutdown agent gracefully.\n */\n shutdown(): void {\n this.running = false\n\n if (this.heartbeatManager) {\n this.heartbeatManager.stop()\n }\n\n console.log(`Agent ${this.name} shutdown`)\n }\n\n /**\n * Check if agent is running.\n */\n isRunning(): boolean {\n return this.running\n }\n}\n","/**\n * Basion Agent SDK - Agent State Client\n * Port of Python basion_agent/agent_state_client.py\n */\n\nimport { APIException } from './exceptions.js'\n\nexport interface AgentStateResponse {\n conversationId: string\n namespace: string\n state: Record<string, unknown>\n createdAt?: string\n updatedAt?: string\n}\n\n/**\n * Async HTTP client for agent-state API.\n * Used for storing framework-specific state (e.g., Pydantic AI messages).\n */\nexport class AgentStateClient {\n private readonly baseUrl: string\n\n constructor(baseUrl: string) {\n this.baseUrl = baseUrl.replace(/\\/$/, '')\n }\n\n /**\n * Store or update agent state.\n */\n async put(\n conversationId: string,\n namespace: string,\n state: Record<string, unknown>\n ): Promise<{ conversationId: string; namespace: string; created: boolean }> {\n const url = `${this.baseUrl}/agent-state/${conversationId}`\n const payload = {\n namespace,\n state,\n }\n\n try {\n const response = await fetch(url, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to store agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversationId: string; namespace: string; created: boolean }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to store agent state: ${error}`)\n }\n }\n\n /**\n * Get agent state.\n */\n async get(\n conversationId: string,\n namespace: string\n ): Promise<AgentStateResponse | null> {\n const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`)\n url.searchParams.set('namespace', namespace)\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get agent state: ${error}`)\n }\n }\n\n /**\n * Delete agent state.\n */\n async delete(\n conversationId: string,\n namespace?: string\n ): Promise<{ conversationId: string; namespace?: string; deleted: number }> {\n const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`)\n if (namespace) {\n url.searchParams.set('namespace', namespace)\n }\n\n try {\n const response = await fetch(url.toString(), { method: 'DELETE' })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to delete agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversationId: string; namespace?: string; deleted: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to delete agent state: ${error}`)\n }\n }\n\n // ── Vercel AI Messages (row-per-message store) ──────────────────────\n\n /**\n * Append Vercel AI messages to a conversation.\n */\n async appendVercelMessages(\n conversationId: string,\n messages: Record<string, unknown>[],\n ): Promise<{ conversation_id: string; appended: number; total: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}/append`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ messages }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to append vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; appended: number; total: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to append vercel messages: ${error}`)\n }\n }\n\n /**\n * Get Vercel AI messages for a conversation.\n * Returns AgentStateResponse-compatible shape or null if not found.\n */\n async getVercelMessages(\n conversationId: string,\n last?: number,\n ): Promise<AgentStateResponse | null> {\n const url = new URL(`${this.baseUrl}/vercel-ai-messages/${conversationId}`)\n if (last !== undefined) {\n url.searchParams.set('last', String(last))\n }\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get vercel messages: ${error}`)\n }\n }\n\n /**\n * Prune Vercel AI messages before a given sequence number.\n * Pruned messages are excluded from load but retained for audit.\n */\n async pruneVercelMessages(\n conversationId: string,\n beforeSequence: number,\n ): Promise<{ conversation_id: string; pruned: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}/prune`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ before_sequence: beforeSequence }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to prune vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; pruned: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to prune vercel messages: ${error}`)\n }\n }\n\n /**\n * Delete all Vercel AI messages for a conversation.\n */\n async deleteVercelMessages(\n conversationId: string,\n ): Promise<{ conversation_id: string; deleted: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}`\n\n try {\n const response = await fetch(url, { method: 'DELETE' })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to delete vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; deleted: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to delete vercel messages: ${error}`)\n }\n }\n\n // ── Filesystem Snapshots (row-per-snapshot store) ─────────────────────\n\n /**\n * Append filesystem snapshots to a conversation.\n */\n async appendFilesystemSnapshots(\n conversationId: string,\n snapshots: Record<string, unknown>[],\n ): Promise<{ conversation_id: string; appended: number; total: number }> {\n const url = `${this.baseUrl}/filesystem-snapshots/${conversationId}/append`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ snapshots }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to append filesystem snapshots: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; appended: number; total: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to append filesystem snapshots: ${error}`)\n }\n }\n\n /**\n * Get filesystem snapshots for a conversation.\n * Returns AgentStateResponse-compatible shape or null if not found.\n */\n async getFilesystemSnapshots(\n conversationId: string,\n ): Promise<AgentStateResponse | null> {\n const url = `${this.baseUrl}/filesystem-snapshots/${conversationId}`\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get filesystem snapshots: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get filesystem snapshots: ${error}`)\n }\n }\n}\n","/**\n * Basion Agent SDK - Vercel AI SDK Extension\n *\n * VercelAIMessageStore for persistent message history with Vercel AI SDK.\n *\n * @packageDocumentation\n */\n\nimport type { BasionAgentApp } from '../app.js'\nimport { _messageContext } from '../agent.js'\nimport { AgentStateClient } from '../agent-state-client.js'\n\n// Type definitions compatible with Vercel AI SDK's CoreMessage\ninterface CoreMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content: string | Array<{ type: string;[key: string]: unknown }>\n [key: string]: unknown\n}\n\n/**\n * Persistent message store for Vercel AI SDK.\n *\n * Stores and retrieves AI SDK message history using the agent-state API.\n * Cross-system message IDs (`_messageId`) are embedded automatically — no\n * manual ID passing required.\n *\n * @example\n * ```typescript\n * import { BasionAgentApp } from 'basion-ai-sdk'\n * import { VercelAIMessageStore } from 'basion-ai-sdk/extensions/vercel-ai'\n * import { streamText } from 'ai'\n * import { anthropic } from '@ai-sdk/anthropic'\n *\n * const app = new BasionAgentApp({ gatewayUrl: 'agent-gateway:8080', apiKey: 'your-key' })\n * const store = new VercelAIMessageStore(app)\n * const agent = await app.registerMe({ ... })\n *\n * agent.onMessage(async (message) => {\n * const history = await store.load(message.conversationId)\n *\n * const messages = [...history, { role: 'user', content: message.content }]\n *\n * const result = await streamText({ model: anthropic('claude-sonnet-4-6'), messages })\n *\n * // _messageId is embedded automatically — no IDs to pass\n * await store.append(message.conversationId, [\n * { role: 'user', content: message.content },\n * ...result.response.messages,\n * ])\n * })\n *\n * app.run()\n * ```\n */\nexport class VercelAIMessageStore {\n private static readonly NAMESPACE = 'vercel_ai'\n private readonly client: AgentStateClient\n\n constructor(app: BasionAgentApp) {\n this.client = new AgentStateClient(app.gatewayClient.conversationStoreUrl)\n }\n\n /**\n * Load message history for a conversation.\n *\n * @param conversationId - The conversation ID to load history for\n * @returns Array of CoreMessage objects (empty array if no history)\n */\n async load(conversationId: string): Promise<CoreMessage[]> {\n const result = await this.client.getVercelMessages(conversationId)\n\n if (!result) {\n return []\n }\n\n const state = result.state ?? {}\n const messages = state.messages as unknown[]\n\n if (!Array.isArray(messages)) {\n return []\n }\n\n return messages.filter(this.isValidMessage) as CoreMessage[]\n }\n\n /**\n * Append new messages to existing history.\n *\n * Cross-system `_messageId` fields are embedded automatically from the\n * current message handler context — no IDs need to be passed manually.\n *\n * @param conversationId - The conversation ID\n * @param newMessages - New messages to append (user turn + response messages)\n */\n async append(conversationId: string, newMessages: CoreMessage[]): Promise<void> {\n const ctx = _messageContext.getStore()\n const tagged = ctx ? this.embedMessageIds(newMessages, ctx) : newMessages\n await this.client.appendVercelMessages(\n conversationId,\n tagged.map(this.serializeMessage),\n )\n }\n\n /**\n * Clear message history for a conversation.\n *\n * @param conversationId - The conversation ID to clear history for\n */\n async clear(conversationId: string): Promise<void> {\n await this.client.deleteVercelMessages(conversationId)\n }\n\n /**\n * Prune messages before a given sequence number.\n *\n * Called after server-side compaction fires. Pruned messages are excluded\n * from `load()` but retained in the database for audit.\n *\n * @param conversationId - The conversation ID\n * @param beforeSequence - Prune all messages with sequence < this value\n * @returns Number of messages pruned\n */\n async pruneBeforeSequence(conversationId: string, beforeSequence: number): Promise<number> {\n const result = await this.client.pruneVercelMessages(conversationId, beforeSequence)\n return result.pruned\n }\n\n /**\n * Get the last N messages from history.\n *\n * @param conversationId - The conversation ID\n * @param count - Number of messages to retrieve\n */\n async getLastMessages(conversationId: string, count: number): Promise<CoreMessage[]> {\n const result = await this.client.getVercelMessages(conversationId, count)\n\n if (!result) {\n return []\n }\n\n const state = result.state ?? {}\n const messages = state.messages as unknown[]\n\n if (!Array.isArray(messages)) {\n return []\n }\n\n return messages.filter(this.isValidMessage) as CoreMessage[]\n }\n\n private embedMessageIds(\n messages: CoreMessage[],\n ids: { userMessageId?: string; responseMessageId?: string },\n ): CoreMessage[] {\n const result = [...messages]\n\n if (ids.userMessageId) {\n const userIdx = result.findIndex((m) => m.role === 'user')\n if (userIdx !== -1) {\n result[userIdx] = { ...result[userIdx], _messageId: ids.userMessageId }\n }\n }\n\n if (ids.responseMessageId) {\n for (let i = result.length - 1; i >= 0; i--) {\n const m = result[i]\n if (\n m.role === 'assistant' &&\n (typeof m.content === 'string'\n ? m.content.length > 0\n : (m.content as Array<{ type: string }>).some((c) => c.type === 'text'))\n ) {\n result[i] = { ...result[i], _messageId: ids.responseMessageId }\n break\n }\n }\n }\n\n return result\n }\n\n private isValidMessage(msg: unknown): msg is CoreMessage {\n if (typeof msg !== 'object' || msg === null) return false\n const m = msg as Record<string, unknown>\n return (\n typeof m.role === 'string' &&\n ['system', 'user', 'assistant', 'tool'].includes(m.role) &&\n (typeof m.content === 'string' || Array.isArray(m.content))\n )\n }\n\n private serializeMessage(msg: CoreMessage): Record<string, unknown> {\n return { ...msg }\n }\n}\n\nexport type { CoreMessage }\n"]}
|
|
@@ -164,6 +164,9 @@ var VfsBackedFs = class _VfsBackedFs {
|
|
|
164
164
|
);
|
|
165
165
|
if (result.status === "noop") return null;
|
|
166
166
|
if (result.status === "merged") {
|
|
167
|
+
if (result.commit_id === this.currentCommitId) {
|
|
168
|
+
await writeSidecar(this.worktreePath, result.commit_id);
|
|
169
|
+
}
|
|
167
170
|
return result.commit_id;
|
|
168
171
|
}
|
|
169
172
|
if (!this.conflictResolver) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/extensions/vfs-sync.ts"],"names":["fs"],"mappings":"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","file":"vfs-sync.js","sourcesContent":["/**\n * Basion Agent SDK — VFS Sync Extension\n *\n * `VfsBackedFs` wraps an existing IFileSystem (typically a Turso/agentfs-sdk\n * instance) and synchronizes it with the VFS service:\n *\n * • On open(): HEAD-check, delta-sync from VFS into the inner FS,\n * and create a per-turn branch.\n * • On writes: Write to inner FS AND push a per-write commit to\n * the VFS turn branch.\n * • On reads: Forward to inner FS (no network).\n * • On flushAndMerge(): Merge the turn branch into main and update the\n * .vfs-commit sidecar.\n *\n * The agent's tools (bash, pdf-ingest, etc.) talk to MountableFs which talks\n * to VfsBackedFs. They have no idea VFS exists.\n *\n * Sandbox mode (when `fromCommit` is set):\n * • Forks from a specific historical commit instead of main HEAD\n * • Skips the .vfs-commit sidecar update\n * • flushAndMerge() is a no-op (sandbox writes never reach main)\n * • close() deletes the worktree directory\n *\n * Caller responsibilities (basion-sdk does NOT do these):\n * • Construct the inner IFileSystem (e.g. via agentfs-sdk + just-bash)\n * • Choose the worktreePath (persistent for normal mode, /tmp for sandbox)\n * • Pass a fresh `messageId` per turn for commit traceability\n * • Provide a conflictResolver if you want LLM-backed conflict resolution\n *\n * What this extension explicitly does NOT do (deferred per PRD §10):\n * • WAL for offline write durability\n * • Offline mode that uses cached state when VFS is unreachable\n * • Built-in LLM conflict resolver\n * • Per-write retry on transient network errors\n */\n\nimport { promises as fs } from 'node:fs'\nimport { join } from 'node:path'\nimport type {\n IFileSystem,\n FileContent,\n BufferEncoding,\n FsStat,\n MkdirOptions,\n RmOptions,\n CpOptions,\n} from 'just-bash'\nimport { VfsClient } from '../vfs-client.js'\nimport type { VfsMergeConflict } from '../vfs-client.js'\n\n// These types are not re-exported from just-bash's main entry; we define\n// them locally to keep our IFileSystem implementation type-compatible.\ninterface ReadFileOptions {\n encoding?: BufferEncoding | null\n}\ninterface WriteFileOptions {\n encoding?: BufferEncoding\n}\ninterface DirentEntry {\n name: string\n isFile: boolean\n isDirectory: boolean\n isSymbolicLink: boolean\n}\n\n// ─── Public types ─────────────────────────────────────────────────────────\n\nexport interface VfsBackedFsOptions {\n /** VFS client (usually app.vfsClient) */\n vfsClient: VfsClient\n\n /** The user's filesystem ID from VFS */\n filesystemId: string\n\n /**\n * The IFileSystem to wrap. Must be mutable — VfsBackedFs writes into it\n * during sync (delta-pull from VFS) and on every agent write.\n *\n * Caller is responsible for creating this (e.g. agentfs-sdk + just-bash).\n */\n innerFs: IFileSystem\n\n /**\n * Persistent local directory where the .vfs-commit sidecar lives.\n * For normal mode: a long-lived path like ~/.basion/worktrees/conv-{id}\n * For sandbox mode: a throwaway path like /tmp/vfs-sandbox/msg-{id}\n *\n * Used only for the sidecar file. The inner FS storage is the caller's concern.\n */\n worktreePath: string\n\n /** Branch name for this turn. e.g. \"turn-{messageId}\" or \"sandbox-{messageId}\" */\n branchName: string\n\n /** Default base branch when fromCommit is null. Default: \"main\" */\n baseBranch?: string\n\n /** Sandbox mode: fork from this specific commit instead of baseBranch HEAD */\n fromCommit?: string\n\n /** Message ID this turn corresponds to — attached to every commit on the branch */\n messageId?: string\n\n /** Free-form metadata attached to every commit on the turn branch */\n commitMetadata?: Record<string, unknown>\n\n /** Optional resolver for merge conflicts. Default: throw VfsMergeConflictError */\n conflictResolver?: ConflictResolver\n}\n\nexport interface ConflictResolver {\n resolve(conflicts: VfsMergeConflict[]): Promise<ResolvedFile[]>\n}\n\nexport interface ResolvedFile {\n path: string\n /** null = delete the file */\n content: Buffer | null\n}\n\nexport class VfsMergeConflictError extends Error {\n constructor(public readonly conflicts: VfsMergeConflict[]) {\n super(`VFS merge has ${conflicts.length} unresolved conflict(s): ${conflicts.map(c => c.path).join(', ')}`)\n this.name = 'VfsMergeConflictError'\n }\n}\n\n// ─── Internal: sidecar helpers ────────────────────────────────────────────\n\nconst SIDECAR_FILENAME = '.vfs-commit'\n\nasync function readSidecar(worktreePath: string): Promise<string | null> {\n try {\n const data = await fs.readFile(join(worktreePath, SIDECAR_FILENAME), 'utf-8')\n const trimmed = data.trim()\n return trimmed.length > 0 ? trimmed : null\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw err\n }\n}\n\nasync function writeSidecar(worktreePath: string, commitId: string): Promise<void> {\n await fs.mkdir(worktreePath, { recursive: true })\n await fs.writeFile(join(worktreePath, SIDECAR_FILENAME), commitId, 'utf-8')\n}\n\n// ─── Helpers for inner-FS content normalization ───────────────────────────\n\nfunction toBuffer(content: FileContent): Buffer {\n if (typeof content === 'string') return Buffer.from(content, 'utf-8')\n if (Buffer.isBuffer(content)) return content\n return Buffer.from(content)\n}\n\n// ─── The wrapper class ────────────────────────────────────────────────────\n\n/**\n * VFS-backed IFileSystem wrapper.\n *\n * Forwards all read operations to the inner FS, intercepts writes/deletes\n * to also push them to a per-turn branch in the VFS service.\n */\nexport class VfsBackedFs implements IFileSystem {\n private readonly vfsClient: VfsClient\n private readonly filesystemId: string\n private readonly innerFs: IFileSystem\n private readonly worktreePath: string\n private readonly branchName: string\n private readonly baseBranch: string\n private readonly isSandbox: boolean\n private readonly messageId: string | undefined\n private readonly commitMetadata: Record<string, unknown> | undefined\n private readonly conflictResolver: ConflictResolver | undefined\n\n private currentCommitId: string | null\n private writeSeq: number\n\n private constructor(opts: {\n vfsClient: VfsClient\n filesystemId: string\n innerFs: IFileSystem\n worktreePath: string\n branchName: string\n baseBranch: string\n isSandbox: boolean\n messageId: string | undefined\n commitMetadata: Record<string, unknown> | undefined\n conflictResolver: ConflictResolver | undefined\n initialCommitId: string | null\n }) {\n this.vfsClient = opts.vfsClient\n this.filesystemId = opts.filesystemId\n this.innerFs = opts.innerFs\n this.worktreePath = opts.worktreePath\n this.branchName = opts.branchName\n this.baseBranch = opts.baseBranch\n this.isSandbox = opts.isSandbox\n this.messageId = opts.messageId\n this.commitMetadata = opts.commitMetadata\n this.conflictResolver = opts.conflictResolver\n this.currentCommitId = opts.initialCommitId\n this.writeSeq = 0\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────\n\n /**\n * Open a VfsBackedFs.\n *\n * Performs HEAD check, delta sync, and turn branch creation atomically.\n */\n static async open(opts: VfsBackedFsOptions): Promise<VfsBackedFs> {\n const baseBranch = opts.baseBranch ?? 'main'\n const isSandbox = !!opts.fromCommit\n\n // Make sure the worktree dir exists (for the sidecar)\n await fs.mkdir(opts.worktreePath, { recursive: true })\n\n let initialCommitId: string | null\n\n if (isSandbox) {\n // Sandbox mode: forking from a specific historical commit.\n // We do NOT touch the persistent sidecar.\n // We DO wipe the inner FS clean and populate from the historical tree.\n await wipeInnerFs(opts.innerFs)\n\n // Pull the tree at the requested commit\n const tree = await opts.vfsClient.getTreeAtCommit(opts.filesystemId, opts.fromCommit!)\n for (const entry of tree) {\n if (entry.type !== 'file') continue\n const blob = await opts.vfsClient.getBlob(entry.hash)\n await ensureParentDirs(opts.innerFs, entry.path)\n await opts.innerFs.writeFile(entry.path, blob)\n }\n\n // Create the sandbox branch from the historical commit\n await opts.vfsClient.createBranch(opts.filesystemId, {\n name: opts.branchName,\n from_commit: opts.fromCommit!,\n })\n\n initialCommitId = opts.fromCommit!\n } else {\n // Normal mode: HEAD check + optional delta sync.\n const localCommit = await readSidecar(opts.worktreePath)\n\n const baseBranchInfo = await opts.vfsClient.getBranch(opts.filesystemId, baseBranch)\n const headCommit = baseBranchInfo.headCommitId\n\n if (localCommit === null && headCommit !== null) {\n // Cold start: full populate from main HEAD\n const tree = await opts.vfsClient.getTree(opts.filesystemId, baseBranch)\n for (const entry of tree) {\n if (entry.type !== 'file') continue\n const blob = await opts.vfsClient.getBlob(entry.hash)\n await ensureParentDirs(opts.innerFs, entry.path)\n await opts.innerFs.writeFile(entry.path, blob)\n }\n await writeSidecar(opts.worktreePath, headCommit)\n } else if (localCommit !== null && headCommit !== null && localCommit !== headCommit) {\n // Stale: delta sync from localCommit → headCommit\n const diff = await opts.vfsClient.diff(opts.filesystemId, localCommit, headCommit)\n\n for (const entry of [...diff.added, ...diff.modified]) {\n const hash = entry.hash ?? entry.to_hash\n if (!hash) continue\n const blob = await opts.vfsClient.getBlob(hash)\n await ensureParentDirs(opts.innerFs, entry.path)\n await opts.innerFs.writeFile(entry.path, blob)\n }\n for (const entry of diff.deleted) {\n if (await opts.innerFs.exists(entry.path)) {\n await opts.innerFs.rm(entry.path, { force: true })\n }\n }\n await writeSidecar(opts.worktreePath, headCommit)\n }\n // else: localCommit === headCommit (warm path) → nothing to do\n\n // Create the per-turn branch from the base branch HEAD\n await opts.vfsClient.createBranch(opts.filesystemId, {\n name: opts.branchName,\n from_branch: baseBranch,\n })\n\n initialCommitId = headCommit\n }\n\n return new VfsBackedFs({\n vfsClient: opts.vfsClient,\n filesystemId: opts.filesystemId,\n innerFs: opts.innerFs,\n worktreePath: opts.worktreePath,\n branchName: opts.branchName,\n baseBranch,\n isSandbox,\n messageId: opts.messageId,\n commitMetadata: opts.commitMetadata,\n conflictResolver: opts.conflictResolver,\n initialCommitId,\n })\n }\n\n /**\n * The latest commit on the per-turn branch.\n * Returns the initial commit ID (or null) if no writes have happened yet.\n */\n getCurrentCommit(): string | null {\n return this.currentCommitId\n }\n\n /**\n * Merge the per-turn branch into the base branch.\n * In sandbox mode, this is a no-op and returns null.\n *\n * Returns the new base branch HEAD commit on success, or null on noop.\n * Throws VfsMergeConflictError if there are unresolvable conflicts and\n * no conflictResolver was provided.\n */\n async flushAndMerge(): Promise<string | null> {\n if (this.isSandbox) {\n // Sandbox writes never reach main\n return null\n }\n\n return this.mergeWithRetry()\n }\n\n private async mergeWithRetry(maxRetries = 3): Promise<string | null> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const result = await this.vfsClient.merge(\n this.filesystemId,\n this.branchName,\n this.baseBranch,\n {\n metadata: this.commitMetadata,\n messageId: this.messageId,\n },\n )\n\n if (result.status === 'noop') return null\n if (result.status === 'merged') {\n // Intentionally do NOT update .vfs-commit here.\n // The local agentFS may not reflect the merged content (e.g.,\n // if VFS did a diff3 merge that combined our changes with\n // another branch's changes). By leaving the sidecar stale,\n // the next turn's HEAD check will detect the mismatch and\n // delta-sync the merged state into the local worktree.\n return result.commit_id\n }\n\n // status === 'conflict'\n if (!this.conflictResolver) {\n throw new VfsMergeConflictError(result.conflicts)\n }\n\n if (attempt === maxRetries) {\n throw new VfsMergeConflictError(result.conflicts)\n }\n\n // Resolve and write to the turn branch, then retry\n const resolved = await this.conflictResolver.resolve(result.conflicts)\n for (const r of resolved) {\n if (r.content === null) {\n await this.vfsClient.deleteFile(\n this.filesystemId,\n this.branchName,\n r.path,\n { messageId: this.messageId },\n )\n } else {\n await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n r.path,\n r.content,\n {\n messageId: this.messageId,\n metadata: { ...this.commitMetadata, conflictResolved: true },\n idempotencyKey: `${this.branchName}-resolve-${this.writeSeq++}`,\n },\n )\n }\n }\n }\n // Unreachable: loop above either returns or throws\n return null\n }\n\n /**\n * Closes the wrapper. In normal mode, the worktree is preserved.\n * In sandbox mode, the worktree directory is deleted.\n */\n async close(): Promise<void> {\n if (this.isSandbox) {\n try {\n await fs.rm(this.worktreePath, { recursive: true, force: true })\n } catch {\n // best effort\n }\n }\n }\n\n // ─── IFileSystem: read methods (forward to inner FS) ──────────────────\n\n readFile(path: string, options?: ReadFileOptions | BufferEncoding): Promise<string> {\n return this.innerFs.readFile(path, options)\n }\n\n readFileBuffer(path: string): Promise<Uint8Array> {\n return this.innerFs.readFileBuffer(path)\n }\n\n exists(path: string): Promise<boolean> {\n return this.innerFs.exists(path)\n }\n\n stat(path: string): Promise<FsStat> {\n return this.innerFs.stat(path)\n }\n\n lstat(path: string): Promise<FsStat> {\n return this.innerFs.lstat(path)\n }\n\n readdir(path: string): Promise<string[]> {\n return this.innerFs.readdir(path)\n }\n\n readdirWithFileTypes(path: string): Promise<DirentEntry[]> {\n if (this.innerFs.readdirWithFileTypes) {\n return this.innerFs.readdirWithFileTypes(path)\n }\n // Fall back: synthesize from readdir + stat\n return this.fallbackReaddirWithFileTypes(path)\n }\n\n private async fallbackReaddirWithFileTypes(path: string): Promise<DirentEntry[]> {\n const names = await this.innerFs.readdir(path)\n const result: DirentEntry[] = []\n for (const name of names) {\n const child = this.innerFs.resolvePath(path, name)\n const s = await this.innerFs.stat(child)\n result.push({\n name,\n isFile: s.isFile,\n isDirectory: s.isDirectory,\n isSymbolicLink: s.isSymbolicLink,\n })\n }\n return result\n }\n\n readlink(path: string): Promise<string> {\n return this.innerFs.readlink(path)\n }\n\n realpath(path: string): Promise<string> {\n return this.innerFs.realpath(path)\n }\n\n resolvePath(base: string, path: string): string {\n return this.innerFs.resolvePath(base, path)\n }\n\n getAllPaths(): string[] {\n return this.innerFs.getAllPaths()\n }\n\n // ─── IFileSystem: write methods (intercept) ───────────────────────────\n\n async writeFile(\n path: string,\n content: FileContent,\n options?: WriteFileOptions | BufferEncoding,\n ): Promise<void> {\n // 1. Local write — instant\n await this.innerFs.writeFile(path, content, options)\n\n // 2. Push to VFS turn branch\n const buf = toBuffer(content)\n const result = await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n normalizePath(path),\n buf,\n {\n messageId: this.messageId,\n metadata: this.commitMetadata,\n idempotencyKey: `${this.branchName}-w${this.writeSeq++}`,\n },\n )\n this.currentCommitId = result.commit_id\n }\n\n async appendFile(\n path: string,\n content: FileContent,\n options?: WriteFileOptions | BufferEncoding,\n ): Promise<void> {\n // 1. Local append\n await this.innerFs.appendFile(path, content, options)\n\n // 2. Read full file and push the new state to VFS\n const fullBuf = await this.innerFs.readFileBuffer(path)\n const result = await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n normalizePath(path),\n Buffer.from(fullBuf),\n {\n messageId: this.messageId,\n metadata: this.commitMetadata,\n idempotencyKey: `${this.branchName}-a${this.writeSeq++}`,\n },\n )\n this.currentCommitId = result.commit_id\n }\n\n async rm(path: string, options?: RmOptions): Promise<void> {\n // Capture what we're about to delete from the inner FS, so we can\n // mirror it in VFS (which only knows about file deletes, not dirs).\n const filesToDelete = await this.collectFilesUnder(path)\n\n await this.innerFs.rm(path, options)\n\n for (const filePath of filesToDelete) {\n try {\n const result = await this.vfsClient.deleteFile(\n this.filesystemId,\n this.branchName,\n normalizePath(filePath),\n { messageId: this.messageId },\n )\n this.currentCommitId = result.commit_id\n } catch (err) {\n // If file doesn't exist on the branch, ignore\n if (!isNotFoundError(err)) throw err\n }\n }\n }\n\n private async collectFilesUnder(path: string): Promise<string[]> {\n try {\n const stat = await this.innerFs.stat(path)\n if (stat.isFile) return [path]\n if (stat.isDirectory) {\n // Walk the dir\n const result: string[] = []\n const stack: string[] = [path]\n while (stack.length > 0) {\n const current = stack.pop()!\n const names = await this.innerFs.readdir(current)\n for (const name of names) {\n const child = this.innerFs.resolvePath(current, name)\n const childStat = await this.innerFs.stat(child)\n if (childStat.isFile) result.push(child)\n else if (childStat.isDirectory) stack.push(child)\n }\n }\n return result\n }\n return []\n } catch {\n return []\n }\n }\n\n async mkdir(path: string, options?: MkdirOptions): Promise<void> {\n // mkdir is a no-op for VFS — directories are implicit, created when\n // a file with that prefix is written. Forward to inner only.\n await this.innerFs.mkdir(path, options)\n }\n\n async cp(src: string, dest: string, options?: CpOptions): Promise<void> {\n // 1. Local copy\n await this.innerFs.cp(src, dest, options)\n\n // 2. Walk the destination, push every new file to VFS\n const destFiles = await this.collectFilesUnder(dest)\n for (const filePath of destFiles) {\n const buf = await this.innerFs.readFileBuffer(filePath)\n const result = await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n normalizePath(filePath),\n Buffer.from(buf),\n {\n messageId: this.messageId,\n metadata: this.commitMetadata,\n idempotencyKey: `${this.branchName}-cp${this.writeSeq++}`,\n },\n )\n this.currentCommitId = result.commit_id\n }\n }\n\n async mv(src: string, dest: string): Promise<void> {\n // VFS has no native rename; emulate as cp + rm.\n // Snapshot what we're moving BEFORE the inner mv, since after the\n // move src no longer exists.\n const filesToMove = await this.collectFilesUnder(src)\n\n await this.innerFs.mv(src, dest)\n\n // Push the destination state and delete source from VFS\n for (const oldPath of filesToMove) {\n // Determine the new path\n const newPath = oldPath === src ? dest : oldPath.replace(src, dest)\n\n try {\n const buf = await this.innerFs.readFileBuffer(newPath)\n const writeResult = await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n normalizePath(newPath),\n Buffer.from(buf),\n {\n messageId: this.messageId,\n metadata: this.commitMetadata,\n idempotencyKey: `${this.branchName}-mv${this.writeSeq++}`,\n },\n )\n this.currentCommitId = writeResult.commit_id\n } catch {\n // skip\n }\n\n try {\n const delResult = await this.vfsClient.deleteFile(\n this.filesystemId,\n this.branchName,\n normalizePath(oldPath),\n { messageId: this.messageId },\n )\n this.currentCommitId = delResult.commit_id\n } catch (err) {\n if (!isNotFoundError(err)) throw err\n }\n }\n }\n\n // ─── IFileSystem: metadata ops (forward only, not tracked in VFS) ─────\n\n chmod(path: string, mode: number): Promise<void> {\n return this.innerFs.chmod(path, mode)\n }\n\n symlink(target: string, linkPath: string): Promise<void> {\n return this.innerFs.symlink(target, linkPath)\n }\n\n link(existingPath: string, newPath: string): Promise<void> {\n return this.innerFs.link(existingPath, newPath)\n }\n\n utimes(path: string, atime: Date, mtime: Date): Promise<void> {\n return this.innerFs.utimes(path, atime, mtime)\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Normalize a path for VFS storage. Strips leading slash since VFS\n * paths are stored without one.\n */\nfunction normalizePath(p: string): string {\n return p.replace(/^\\/+/, '')\n}\n\n/**\n * Make sure all parent directories exist in the inner FS before writing.\n */\nasync function ensureParentDirs(innerFs: IFileSystem, path: string): Promise<void> {\n const idx = path.lastIndexOf('/')\n if (idx <= 0) return\n const parent = path.slice(0, idx)\n if (await innerFs.exists(parent)) return\n await innerFs.mkdir(parent, { recursive: true })\n}\n\n/**\n * Recursively delete everything in the inner FS at root.\n * Used for sandbox mode initialization.\n */\nasync function wipeInnerFs(innerFs: IFileSystem): Promise<void> {\n let entries: string[] = []\n try {\n entries = await innerFs.readdir('/')\n } catch {\n return\n }\n for (const name of entries) {\n try {\n await innerFs.rm(`/${name}`, { recursive: true, force: true })\n } catch {\n // best effort\n }\n }\n}\n\nfunction isNotFoundError(err: unknown): boolean {\n if (err && typeof err === 'object' && 'statusCode' in err) {\n return (err as { statusCode: number }).statusCode === 404\n }\n return false\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/extensions/vfs-sync.ts"],"names":["fs"],"mappings":"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","file":"vfs-sync.js","sourcesContent":["/**\n * Basion Agent SDK — VFS Sync Extension\n *\n * `VfsBackedFs` wraps an existing IFileSystem (typically a Turso/agentfs-sdk\n * instance) and synchronizes it with the VFS service:\n *\n * • On open(): HEAD-check, delta-sync from VFS into the inner FS,\n * and create a per-turn branch.\n * • On writes: Write to inner FS AND push a per-write commit to\n * the VFS turn branch.\n * • On reads: Forward to inner FS (no network).\n * • On flushAndMerge(): Merge the turn branch into main and update the\n * .vfs-commit sidecar.\n *\n * The agent's tools (bash, pdf-ingest, etc.) talk to MountableFs which talks\n * to VfsBackedFs. They have no idea VFS exists.\n *\n * Sandbox mode (when `fromCommit` is set):\n * • Forks from a specific historical commit instead of main HEAD\n * • Skips the .vfs-commit sidecar update\n * • flushAndMerge() is a no-op (sandbox writes never reach main)\n * • close() deletes the worktree directory\n *\n * Caller responsibilities (basion-sdk does NOT do these):\n * • Construct the inner IFileSystem (e.g. via agentfs-sdk + just-bash)\n * • Choose the worktreePath (persistent for normal mode, /tmp for sandbox)\n * • Pass a fresh `messageId` per turn for commit traceability\n * • Provide a conflictResolver if you want LLM-backed conflict resolution\n *\n * What this extension explicitly does NOT do (deferred per PRD §10):\n * • WAL for offline write durability\n * • Offline mode that uses cached state when VFS is unreachable\n * • Built-in LLM conflict resolver\n * • Per-write retry on transient network errors\n */\n\nimport { promises as fs } from 'node:fs'\nimport { join } from 'node:path'\nimport type {\n IFileSystem,\n FileContent,\n BufferEncoding,\n FsStat,\n MkdirOptions,\n RmOptions,\n CpOptions,\n} from 'just-bash'\nimport { VfsClient } from '../vfs-client.js'\nimport type { VfsMergeConflict } from '../vfs-client.js'\n\n// These types are not re-exported from just-bash's main entry; we define\n// them locally to keep our IFileSystem implementation type-compatible.\ninterface ReadFileOptions {\n encoding?: BufferEncoding | null\n}\ninterface WriteFileOptions {\n encoding?: BufferEncoding\n}\ninterface DirentEntry {\n name: string\n isFile: boolean\n isDirectory: boolean\n isSymbolicLink: boolean\n}\n\n// ─── Public types ─────────────────────────────────────────────────────────\n\nexport interface VfsBackedFsOptions {\n /** VFS client (usually app.vfsClient) */\n vfsClient: VfsClient\n\n /** The user's filesystem ID from VFS */\n filesystemId: string\n\n /**\n * The IFileSystem to wrap. Must be mutable — VfsBackedFs writes into it\n * during sync (delta-pull from VFS) and on every agent write.\n *\n * Caller is responsible for creating this (e.g. agentfs-sdk + just-bash).\n */\n innerFs: IFileSystem\n\n /**\n * Persistent local directory where the .vfs-commit sidecar lives.\n * For normal mode: a long-lived path like ~/.basion/worktrees/conv-{id}\n * For sandbox mode: a throwaway path like /tmp/vfs-sandbox/msg-{id}\n *\n * Used only for the sidecar file. The inner FS storage is the caller's concern.\n */\n worktreePath: string\n\n /** Branch name for this turn. e.g. \"turn-{messageId}\" or \"sandbox-{messageId}\" */\n branchName: string\n\n /** Default base branch when fromCommit is null. Default: \"main\" */\n baseBranch?: string\n\n /** Sandbox mode: fork from this specific commit instead of baseBranch HEAD */\n fromCommit?: string\n\n /** Message ID this turn corresponds to — attached to every commit on the branch */\n messageId?: string\n\n /** Free-form metadata attached to every commit on the turn branch */\n commitMetadata?: Record<string, unknown>\n\n /** Optional resolver for merge conflicts. Default: throw VfsMergeConflictError */\n conflictResolver?: ConflictResolver\n}\n\nexport interface ConflictResolver {\n resolve(conflicts: VfsMergeConflict[]): Promise<ResolvedFile[]>\n}\n\nexport interface ResolvedFile {\n path: string\n /** null = delete the file */\n content: Buffer | null\n}\n\nexport class VfsMergeConflictError extends Error {\n constructor(public readonly conflicts: VfsMergeConflict[]) {\n super(`VFS merge has ${conflicts.length} unresolved conflict(s): ${conflicts.map(c => c.path).join(', ')}`)\n this.name = 'VfsMergeConflictError'\n }\n}\n\n// ─── Internal: sidecar helpers ────────────────────────────────────────────\n\nconst SIDECAR_FILENAME = '.vfs-commit'\n\nasync function readSidecar(worktreePath: string): Promise<string | null> {\n try {\n const data = await fs.readFile(join(worktreePath, SIDECAR_FILENAME), 'utf-8')\n const trimmed = data.trim()\n return trimmed.length > 0 ? trimmed : null\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw err\n }\n}\n\nasync function writeSidecar(worktreePath: string, commitId: string): Promise<void> {\n await fs.mkdir(worktreePath, { recursive: true })\n await fs.writeFile(join(worktreePath, SIDECAR_FILENAME), commitId, 'utf-8')\n}\n\n// ─── Helpers for inner-FS content normalization ───────────────────────────\n\nfunction toBuffer(content: FileContent): Buffer {\n if (typeof content === 'string') return Buffer.from(content, 'utf-8')\n if (Buffer.isBuffer(content)) return content\n return Buffer.from(content)\n}\n\n// ─── The wrapper class ────────────────────────────────────────────────────\n\n/**\n * VFS-backed IFileSystem wrapper.\n *\n * Forwards all read operations to the inner FS, intercepts writes/deletes\n * to also push them to a per-turn branch in the VFS service.\n */\nexport class VfsBackedFs implements IFileSystem {\n private readonly vfsClient: VfsClient\n private readonly filesystemId: string\n private readonly innerFs: IFileSystem\n private readonly worktreePath: string\n private readonly branchName: string\n private readonly baseBranch: string\n private readonly isSandbox: boolean\n private readonly messageId: string | undefined\n private readonly commitMetadata: Record<string, unknown> | undefined\n private readonly conflictResolver: ConflictResolver | undefined\n\n private currentCommitId: string | null\n private writeSeq: number\n\n private constructor(opts: {\n vfsClient: VfsClient\n filesystemId: string\n innerFs: IFileSystem\n worktreePath: string\n branchName: string\n baseBranch: string\n isSandbox: boolean\n messageId: string | undefined\n commitMetadata: Record<string, unknown> | undefined\n conflictResolver: ConflictResolver | undefined\n initialCommitId: string | null\n }) {\n this.vfsClient = opts.vfsClient\n this.filesystemId = opts.filesystemId\n this.innerFs = opts.innerFs\n this.worktreePath = opts.worktreePath\n this.branchName = opts.branchName\n this.baseBranch = opts.baseBranch\n this.isSandbox = opts.isSandbox\n this.messageId = opts.messageId\n this.commitMetadata = opts.commitMetadata\n this.conflictResolver = opts.conflictResolver\n this.currentCommitId = opts.initialCommitId\n this.writeSeq = 0\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────\n\n /**\n * Open a VfsBackedFs.\n *\n * Performs HEAD check, delta sync, and turn branch creation atomically.\n */\n static async open(opts: VfsBackedFsOptions): Promise<VfsBackedFs> {\n const baseBranch = opts.baseBranch ?? 'main'\n const isSandbox = !!opts.fromCommit\n\n // Make sure the worktree dir exists (for the sidecar)\n await fs.mkdir(opts.worktreePath, { recursive: true })\n\n let initialCommitId: string | null\n\n if (isSandbox) {\n // Sandbox mode: forking from a specific historical commit.\n // We do NOT touch the persistent sidecar.\n // We DO wipe the inner FS clean and populate from the historical tree.\n await wipeInnerFs(opts.innerFs)\n\n // Pull the tree at the requested commit\n const tree = await opts.vfsClient.getTreeAtCommit(opts.filesystemId, opts.fromCommit!)\n for (const entry of tree) {\n if (entry.type !== 'file') continue\n const blob = await opts.vfsClient.getBlob(entry.hash)\n await ensureParentDirs(opts.innerFs, entry.path)\n await opts.innerFs.writeFile(entry.path, blob)\n }\n\n // Create the sandbox branch from the historical commit\n await opts.vfsClient.createBranch(opts.filesystemId, {\n name: opts.branchName,\n from_commit: opts.fromCommit!,\n })\n\n initialCommitId = opts.fromCommit!\n } else {\n // Normal mode: HEAD check + optional delta sync.\n const localCommit = await readSidecar(opts.worktreePath)\n\n const baseBranchInfo = await opts.vfsClient.getBranch(opts.filesystemId, baseBranch)\n const headCommit = baseBranchInfo.headCommitId\n\n if (localCommit === null && headCommit !== null) {\n // Cold start: full populate from main HEAD\n const tree = await opts.vfsClient.getTree(opts.filesystemId, baseBranch)\n for (const entry of tree) {\n if (entry.type !== 'file') continue\n const blob = await opts.vfsClient.getBlob(entry.hash)\n await ensureParentDirs(opts.innerFs, entry.path)\n await opts.innerFs.writeFile(entry.path, blob)\n }\n await writeSidecar(opts.worktreePath, headCommit)\n } else if (localCommit !== null && headCommit !== null && localCommit !== headCommit) {\n // Stale: delta sync from localCommit → headCommit\n const diff = await opts.vfsClient.diff(opts.filesystemId, localCommit, headCommit)\n\n for (const entry of [...diff.added, ...diff.modified]) {\n const hash = entry.hash ?? entry.to_hash\n if (!hash) continue\n const blob = await opts.vfsClient.getBlob(hash)\n await ensureParentDirs(opts.innerFs, entry.path)\n await opts.innerFs.writeFile(entry.path, blob)\n }\n for (const entry of diff.deleted) {\n if (await opts.innerFs.exists(entry.path)) {\n await opts.innerFs.rm(entry.path, { force: true })\n }\n }\n await writeSidecar(opts.worktreePath, headCommit)\n }\n // else: localCommit === headCommit (warm path) → nothing to do\n\n // Create the per-turn branch from the base branch HEAD\n await opts.vfsClient.createBranch(opts.filesystemId, {\n name: opts.branchName,\n from_branch: baseBranch,\n })\n\n initialCommitId = headCommit\n }\n\n return new VfsBackedFs({\n vfsClient: opts.vfsClient,\n filesystemId: opts.filesystemId,\n innerFs: opts.innerFs,\n worktreePath: opts.worktreePath,\n branchName: opts.branchName,\n baseBranch,\n isSandbox,\n messageId: opts.messageId,\n commitMetadata: opts.commitMetadata,\n conflictResolver: opts.conflictResolver,\n initialCommitId,\n })\n }\n\n /**\n * The latest commit on the per-turn branch.\n * Returns the initial commit ID (or null) if no writes have happened yet.\n */\n getCurrentCommit(): string | null {\n return this.currentCommitId\n }\n\n /**\n * Merge the per-turn branch into the base branch.\n * In sandbox mode, this is a no-op and returns null.\n *\n * Returns the new base branch HEAD commit on success, or null on noop.\n * Throws VfsMergeConflictError if there are unresolvable conflicts and\n * no conflictResolver was provided.\n */\n async flushAndMerge(): Promise<string | null> {\n if (this.isSandbox) {\n // Sandbox writes never reach main\n return null\n }\n\n return this.mergeWithRetry()\n }\n\n private async mergeWithRetry(maxRetries = 3): Promise<string | null> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const result = await this.vfsClient.merge(\n this.filesystemId,\n this.branchName,\n this.baseBranch,\n {\n metadata: this.commitMetadata,\n messageId: this.messageId,\n },\n )\n\n if (result.status === 'noop') return null\n if (result.status === 'merged') {\n // Update .vfs-commit ONLY when the merge was a fast-forward —\n // the VFS service reports this by returning our turn branch's\n // head as commit_id (see merge.service.ts isAncestor path).\n // In an FF, no three-way merge ran, no conflicts were resolved,\n // and main is now exactly our commits — our local inner FS\n // already matches main HEAD, so the sidecar can safely advance.\n //\n // In a three-way merge, the service creates a NEW commit with\n // diff3/LLM-resolved content. That content is not in our local\n // inner FS, so we intentionally leave the sidecar stale — the\n // next open() will detect the mismatch and delta-sync the\n // merged state into the local worktree.\n if (result.commit_id === this.currentCommitId) {\n await writeSidecar(this.worktreePath, result.commit_id)\n }\n return result.commit_id\n }\n\n // status === 'conflict'\n if (!this.conflictResolver) {\n throw new VfsMergeConflictError(result.conflicts)\n }\n\n if (attempt === maxRetries) {\n throw new VfsMergeConflictError(result.conflicts)\n }\n\n // Resolve and write to the turn branch, then retry\n const resolved = await this.conflictResolver.resolve(result.conflicts)\n for (const r of resolved) {\n if (r.content === null) {\n await this.vfsClient.deleteFile(\n this.filesystemId,\n this.branchName,\n r.path,\n { messageId: this.messageId },\n )\n } else {\n await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n r.path,\n r.content,\n {\n messageId: this.messageId,\n metadata: { ...this.commitMetadata, conflictResolved: true },\n idempotencyKey: `${this.branchName}-resolve-${this.writeSeq++}`,\n },\n )\n }\n }\n }\n // Unreachable: loop above either returns or throws\n return null\n }\n\n /**\n * Closes the wrapper. In normal mode, the worktree is preserved.\n * In sandbox mode, the worktree directory is deleted.\n */\n async close(): Promise<void> {\n if (this.isSandbox) {\n try {\n await fs.rm(this.worktreePath, { recursive: true, force: true })\n } catch {\n // best effort\n }\n }\n }\n\n // ─── IFileSystem: read methods (forward to inner FS) ──────────────────\n\n readFile(path: string, options?: ReadFileOptions | BufferEncoding): Promise<string> {\n return this.innerFs.readFile(path, options)\n }\n\n readFileBuffer(path: string): Promise<Uint8Array> {\n return this.innerFs.readFileBuffer(path)\n }\n\n exists(path: string): Promise<boolean> {\n return this.innerFs.exists(path)\n }\n\n stat(path: string): Promise<FsStat> {\n return this.innerFs.stat(path)\n }\n\n lstat(path: string): Promise<FsStat> {\n return this.innerFs.lstat(path)\n }\n\n readdir(path: string): Promise<string[]> {\n return this.innerFs.readdir(path)\n }\n\n readdirWithFileTypes(path: string): Promise<DirentEntry[]> {\n if (this.innerFs.readdirWithFileTypes) {\n return this.innerFs.readdirWithFileTypes(path)\n }\n // Fall back: synthesize from readdir + stat\n return this.fallbackReaddirWithFileTypes(path)\n }\n\n private async fallbackReaddirWithFileTypes(path: string): Promise<DirentEntry[]> {\n const names = await this.innerFs.readdir(path)\n const result: DirentEntry[] = []\n for (const name of names) {\n const child = this.innerFs.resolvePath(path, name)\n const s = await this.innerFs.stat(child)\n result.push({\n name,\n isFile: s.isFile,\n isDirectory: s.isDirectory,\n isSymbolicLink: s.isSymbolicLink,\n })\n }\n return result\n }\n\n readlink(path: string): Promise<string> {\n return this.innerFs.readlink(path)\n }\n\n realpath(path: string): Promise<string> {\n return this.innerFs.realpath(path)\n }\n\n resolvePath(base: string, path: string): string {\n return this.innerFs.resolvePath(base, path)\n }\n\n getAllPaths(): string[] {\n return this.innerFs.getAllPaths()\n }\n\n // ─── IFileSystem: write methods (intercept) ───────────────────────────\n\n async writeFile(\n path: string,\n content: FileContent,\n options?: WriteFileOptions | BufferEncoding,\n ): Promise<void> {\n // 1. Local write — instant\n await this.innerFs.writeFile(path, content, options)\n\n // 2. Push to VFS turn branch\n const buf = toBuffer(content)\n const result = await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n normalizePath(path),\n buf,\n {\n messageId: this.messageId,\n metadata: this.commitMetadata,\n idempotencyKey: `${this.branchName}-w${this.writeSeq++}`,\n },\n )\n this.currentCommitId = result.commit_id\n }\n\n async appendFile(\n path: string,\n content: FileContent,\n options?: WriteFileOptions | BufferEncoding,\n ): Promise<void> {\n // 1. Local append\n await this.innerFs.appendFile(path, content, options)\n\n // 2. Read full file and push the new state to VFS\n const fullBuf = await this.innerFs.readFileBuffer(path)\n const result = await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n normalizePath(path),\n Buffer.from(fullBuf),\n {\n messageId: this.messageId,\n metadata: this.commitMetadata,\n idempotencyKey: `${this.branchName}-a${this.writeSeq++}`,\n },\n )\n this.currentCommitId = result.commit_id\n }\n\n async rm(path: string, options?: RmOptions): Promise<void> {\n // Capture what we're about to delete from the inner FS, so we can\n // mirror it in VFS (which only knows about file deletes, not dirs).\n const filesToDelete = await this.collectFilesUnder(path)\n\n await this.innerFs.rm(path, options)\n\n for (const filePath of filesToDelete) {\n try {\n const result = await this.vfsClient.deleteFile(\n this.filesystemId,\n this.branchName,\n normalizePath(filePath),\n { messageId: this.messageId },\n )\n this.currentCommitId = result.commit_id\n } catch (err) {\n // If file doesn't exist on the branch, ignore\n if (!isNotFoundError(err)) throw err\n }\n }\n }\n\n private async collectFilesUnder(path: string): Promise<string[]> {\n try {\n const stat = await this.innerFs.stat(path)\n if (stat.isFile) return [path]\n if (stat.isDirectory) {\n // Walk the dir\n const result: string[] = []\n const stack: string[] = [path]\n while (stack.length > 0) {\n const current = stack.pop()!\n const names = await this.innerFs.readdir(current)\n for (const name of names) {\n const child = this.innerFs.resolvePath(current, name)\n const childStat = await this.innerFs.stat(child)\n if (childStat.isFile) result.push(child)\n else if (childStat.isDirectory) stack.push(child)\n }\n }\n return result\n }\n return []\n } catch {\n return []\n }\n }\n\n async mkdir(path: string, options?: MkdirOptions): Promise<void> {\n // mkdir is a no-op for VFS — directories are implicit, created when\n // a file with that prefix is written. Forward to inner only.\n await this.innerFs.mkdir(path, options)\n }\n\n async cp(src: string, dest: string, options?: CpOptions): Promise<void> {\n // 1. Local copy\n await this.innerFs.cp(src, dest, options)\n\n // 2. Walk the destination, push every new file to VFS\n const destFiles = await this.collectFilesUnder(dest)\n for (const filePath of destFiles) {\n const buf = await this.innerFs.readFileBuffer(filePath)\n const result = await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n normalizePath(filePath),\n Buffer.from(buf),\n {\n messageId: this.messageId,\n metadata: this.commitMetadata,\n idempotencyKey: `${this.branchName}-cp${this.writeSeq++}`,\n },\n )\n this.currentCommitId = result.commit_id\n }\n }\n\n async mv(src: string, dest: string): Promise<void> {\n // VFS has no native rename; emulate as cp + rm.\n // Snapshot what we're moving BEFORE the inner mv, since after the\n // move src no longer exists.\n const filesToMove = await this.collectFilesUnder(src)\n\n await this.innerFs.mv(src, dest)\n\n // Push the destination state and delete source from VFS\n for (const oldPath of filesToMove) {\n // Determine the new path\n const newPath = oldPath === src ? dest : oldPath.replace(src, dest)\n\n try {\n const buf = await this.innerFs.readFileBuffer(newPath)\n const writeResult = await this.vfsClient.writeFile(\n this.filesystemId,\n this.branchName,\n normalizePath(newPath),\n Buffer.from(buf),\n {\n messageId: this.messageId,\n metadata: this.commitMetadata,\n idempotencyKey: `${this.branchName}-mv${this.writeSeq++}`,\n },\n )\n this.currentCommitId = writeResult.commit_id\n } catch {\n // skip\n }\n\n try {\n const delResult = await this.vfsClient.deleteFile(\n this.filesystemId,\n this.branchName,\n normalizePath(oldPath),\n { messageId: this.messageId },\n )\n this.currentCommitId = delResult.commit_id\n } catch (err) {\n if (!isNotFoundError(err)) throw err\n }\n }\n }\n\n // ─── IFileSystem: metadata ops (forward only, not tracked in VFS) ─────\n\n chmod(path: string, mode: number): Promise<void> {\n return this.innerFs.chmod(path, mode)\n }\n\n symlink(target: string, linkPath: string): Promise<void> {\n return this.innerFs.symlink(target, linkPath)\n }\n\n link(existingPath: string, newPath: string): Promise<void> {\n return this.innerFs.link(existingPath, newPath)\n }\n\n utimes(path: string, atime: Date, mtime: Date): Promise<void> {\n return this.innerFs.utimes(path, atime, mtime)\n }\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────\n\n/**\n * Normalize a path for VFS storage. Strips leading slash since VFS\n * paths are stored without one.\n */\nfunction normalizePath(p: string): string {\n return p.replace(/^\\/+/, '')\n}\n\n/**\n * Make sure all parent directories exist in the inner FS before writing.\n */\nasync function ensureParentDirs(innerFs: IFileSystem, path: string): Promise<void> {\n const idx = path.lastIndexOf('/')\n if (idx <= 0) return\n const parent = path.slice(0, idx)\n if (await innerFs.exists(parent)) return\n await innerFs.mkdir(parent, { recursive: true })\n}\n\n/**\n * Recursively delete everything in the inner FS at root.\n * Used for sandbox mode initialization.\n */\nasync function wipeInnerFs(innerFs: IFileSystem): Promise<void> {\n let entries: string[] = []\n try {\n entries = await innerFs.readdir('/')\n } catch {\n return\n }\n for (const name of entries) {\n try {\n await innerFs.rm(`/${name}`, { recursive: true, force: true })\n } catch {\n // best effort\n }\n }\n}\n\nfunction isNotFoundError(err: unknown): boolean {\n if (err && typeof err === 'object' && 'statusCode' in err) {\n return (err as { statusCode: number }).statusCode === 404\n }\n return false\n}\n"]}
|