@yushaw/sanqian-ai-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1393 -0
- package/dist/index.d.ts +1393 -0
- package/dist/index.js +2117 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2065 -0
- package/dist/index.mjs.map +1 -0
- package/dist/testing.d.mts +363 -0
- package/dist/testing.d.ts +363 -0
- package/dist/testing.js +221 -0
- package/dist/testing.js.map +1 -0
- package/dist/testing.mjs +192 -0
- package/dist/testing.mjs.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
EMPTY_CAPABILITIES: () => EMPTY_CAPABILITIES,
|
|
24
|
+
MockSanqianSdk: () => MockSanqianSdk,
|
|
25
|
+
SanqianEmbeddingProvider: () => SanqianEmbeddingProvider,
|
|
26
|
+
SanqianProvider: () => SanqianProvider,
|
|
27
|
+
SanqianRerankProvider: () => SanqianRerankProvider,
|
|
28
|
+
VercelEmbeddingProvider: () => VercelEmbeddingProvider,
|
|
29
|
+
VercelProvider: () => VercelProvider,
|
|
30
|
+
convertSdkStreamEvent: () => convertSdkStreamEvent,
|
|
31
|
+
convertToolsForVercel: () => convertToolsForVercel,
|
|
32
|
+
createActiveWorkTracker: () => createActiveWorkTracker,
|
|
33
|
+
createCategoryPermissionPolicy: () => createCategoryPermissionPolicy,
|
|
34
|
+
createChatRuntimeBridge: () => createChatRuntimeBridge,
|
|
35
|
+
createDefaultPermissionPolicy: () => createDefaultPermissionPolicy,
|
|
36
|
+
createDisabledState: () => createDisabledState,
|
|
37
|
+
createEmbeddingSession: () => createEmbeddingSession,
|
|
38
|
+
createMemoryConversationStore: () => createMemoryConversationStore,
|
|
39
|
+
createMemorySecretStore: () => createMemorySecretStore,
|
|
40
|
+
createOutputOperationsManager: () => createOutputOperationsManager,
|
|
41
|
+
createPermissionGate: () => createPermissionGate,
|
|
42
|
+
createSanqianAgentIdResolver: () => createSanqianAgentIdResolver,
|
|
43
|
+
createSessionManager: () => createSessionManager,
|
|
44
|
+
createTaskPipeline: () => createTaskPipeline,
|
|
45
|
+
createToolRegistry: () => createToolRegistry,
|
|
46
|
+
redactToolError: () => redactToolError,
|
|
47
|
+
resolveAiFeaturesFromRuntime: () => resolveAiFeaturesFromRuntime,
|
|
48
|
+
validateOutputContent: () => validateOutputContent
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(src_exports);
|
|
51
|
+
|
|
52
|
+
// src/contracts/tools.ts
|
|
53
|
+
function createToolRegistry() {
|
|
54
|
+
const tools = /* @__PURE__ */ new Map();
|
|
55
|
+
return {
|
|
56
|
+
register(definition, handler, metadata) {
|
|
57
|
+
tools.set(definition.name, {
|
|
58
|
+
definition,
|
|
59
|
+
handler,
|
|
60
|
+
metadata: {
|
|
61
|
+
category: metadata?.category ?? "read-only",
|
|
62
|
+
readOnlyHint: metadata?.readOnlyHint,
|
|
63
|
+
destructiveHint: metadata?.destructiveHint,
|
|
64
|
+
idempotentHint: metadata?.idempotentHint
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
get(name) {
|
|
69
|
+
return tools.get(name);
|
|
70
|
+
},
|
|
71
|
+
list() {
|
|
72
|
+
return Array.from(tools.values());
|
|
73
|
+
},
|
|
74
|
+
async execute(input) {
|
|
75
|
+
const registration = tools.get(input.name);
|
|
76
|
+
if (!registration) {
|
|
77
|
+
return {
|
|
78
|
+
toolCallId: input.toolCallId,
|
|
79
|
+
success: false,
|
|
80
|
+
error: `Unknown tool: ${input.name}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const result = await registration.handler(input.args, {
|
|
85
|
+
toolCallId: input.toolCallId,
|
|
86
|
+
signal: input.signal
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
toolCallId: input.toolCallId,
|
|
90
|
+
result,
|
|
91
|
+
success: true
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : "Tool execution failed";
|
|
95
|
+
return {
|
|
96
|
+
toolCallId: input.toolCallId,
|
|
97
|
+
success: false,
|
|
98
|
+
error: message
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/contracts/permissions.ts
|
|
106
|
+
function createPermissionGate(policy) {
|
|
107
|
+
const pending = /* @__PURE__ */ new Map();
|
|
108
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
109
|
+
let counter = 0;
|
|
110
|
+
return {
|
|
111
|
+
async requestPermission(input) {
|
|
112
|
+
const decision = await policy.evaluate(input);
|
|
113
|
+
if (decision.type !== "ask") {
|
|
114
|
+
return decision;
|
|
115
|
+
}
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
counter++;
|
|
118
|
+
const id = `perm-${counter}`;
|
|
119
|
+
const entry = {
|
|
120
|
+
id,
|
|
121
|
+
toolCallId: input.toolCallId,
|
|
122
|
+
toolName: input.name,
|
|
123
|
+
args: input.args,
|
|
124
|
+
createdAt: Date.now(),
|
|
125
|
+
resolve: (userDecision) => {
|
|
126
|
+
pending.delete(id);
|
|
127
|
+
if (userDecision === "approve") {
|
|
128
|
+
resolve({ type: "allow" });
|
|
129
|
+
} else if (userDecision === "deny") {
|
|
130
|
+
resolve({ type: "deny", reason: "User denied" });
|
|
131
|
+
} else {
|
|
132
|
+
resolve({ type: "deny", reason: "Cancelled" });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
pending.set(id, entry);
|
|
137
|
+
for (const listener of listeners) {
|
|
138
|
+
listener(entry);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
resolvePermission(id, decision) {
|
|
143
|
+
const entry = pending.get(id);
|
|
144
|
+
if (entry) {
|
|
145
|
+
entry.resolve(decision);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
cancelAll() {
|
|
149
|
+
for (const entry of pending.values()) {
|
|
150
|
+
entry.resolve("cancel");
|
|
151
|
+
}
|
|
152
|
+
pending.clear();
|
|
153
|
+
},
|
|
154
|
+
listPending() {
|
|
155
|
+
return Array.from(pending.values());
|
|
156
|
+
},
|
|
157
|
+
onPermissionRequest(listener) {
|
|
158
|
+
listeners.add(listener);
|
|
159
|
+
return () => listeners.delete(listener);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function createDefaultPermissionPolicy() {
|
|
164
|
+
return {
|
|
165
|
+
async evaluate() {
|
|
166
|
+
return { type: "allow" };
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function createCategoryPermissionPolicy(registry) {
|
|
171
|
+
const mutationCategories = [
|
|
172
|
+
"note-mutation",
|
|
173
|
+
"notebook-mutation",
|
|
174
|
+
"local-file-write",
|
|
175
|
+
"dangerous"
|
|
176
|
+
];
|
|
177
|
+
return {
|
|
178
|
+
async evaluate(input) {
|
|
179
|
+
const tool = registry.get(input.name);
|
|
180
|
+
if (!tool) {
|
|
181
|
+
return { type: "deny", reason: `Unknown tool: ${input.name}` };
|
|
182
|
+
}
|
|
183
|
+
if (mutationCategories.includes(tool.metadata.category)) {
|
|
184
|
+
return {
|
|
185
|
+
type: "ask",
|
|
186
|
+
interrupt: {
|
|
187
|
+
type: "approval_request",
|
|
188
|
+
payload: {
|
|
189
|
+
tool: input.name,
|
|
190
|
+
args: input.args,
|
|
191
|
+
reason: `Tool "${input.name}" requires approval (category: ${tool.metadata.category})`
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return { type: "allow" };
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function redactToolError(error) {
|
|
201
|
+
let redacted = error;
|
|
202
|
+
redacted = redacted.replace(/\n\s+at\s+.+/g, "");
|
|
203
|
+
redacted = redacted.replace(/(?:\/[\w.@-]+){2,}/g, "[path]");
|
|
204
|
+
redacted = redacted.replace(/[A-Z]:\\[\w.\\-]+/g, "[path]");
|
|
205
|
+
redacted = redacted.replace(/SQLITE_\w+|sqlite3?_\w+/gi, "[db-error]");
|
|
206
|
+
return redacted.trim();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/contracts/conversations.ts
|
|
210
|
+
function createMemoryConversationStore() {
|
|
211
|
+
const conversations = /* @__PURE__ */ new Map();
|
|
212
|
+
let counter = 0;
|
|
213
|
+
return {
|
|
214
|
+
async list(input) {
|
|
215
|
+
let items = Array.from(conversations.values());
|
|
216
|
+
if (input.providerId) {
|
|
217
|
+
items = items.filter((c) => c.providerId === input.providerId);
|
|
218
|
+
}
|
|
219
|
+
if (input.agentId) {
|
|
220
|
+
items = items.filter((c) => c.agentId === input.agentId);
|
|
221
|
+
}
|
|
222
|
+
items.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
223
|
+
const offset = input.offset ?? 0;
|
|
224
|
+
const limit = input.limit ?? 20;
|
|
225
|
+
return {
|
|
226
|
+
conversations: items.slice(offset, offset + limit),
|
|
227
|
+
total: items.length
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
async get(id, input) {
|
|
231
|
+
const conv = conversations.get(id);
|
|
232
|
+
if (!conv) return null;
|
|
233
|
+
if (input?.messageLimit) {
|
|
234
|
+
return {
|
|
235
|
+
...conv,
|
|
236
|
+
messages: conv.messages.slice(-input.messageLimit)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return conv;
|
|
240
|
+
},
|
|
241
|
+
async create(input) {
|
|
242
|
+
counter++;
|
|
243
|
+
const id = `conv-${counter}`;
|
|
244
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
245
|
+
const detail = {
|
|
246
|
+
id,
|
|
247
|
+
providerId: input.providerId,
|
|
248
|
+
agentId: input.agentId,
|
|
249
|
+
title: input.title ?? "Untitled",
|
|
250
|
+
createdAt: now,
|
|
251
|
+
updatedAt: now,
|
|
252
|
+
messageCount: 0,
|
|
253
|
+
messages: []
|
|
254
|
+
};
|
|
255
|
+
conversations.set(id, detail);
|
|
256
|
+
return detail;
|
|
257
|
+
},
|
|
258
|
+
async appendMessages(id, messages) {
|
|
259
|
+
const conv = conversations.get(id);
|
|
260
|
+
if (!conv) throw new Error(`Conversation not found: ${id}`);
|
|
261
|
+
conv.messages.push(...messages);
|
|
262
|
+
conv.messageCount = conv.messages.length;
|
|
263
|
+
conv.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
264
|
+
},
|
|
265
|
+
async update(id, patch) {
|
|
266
|
+
const conv = conversations.get(id);
|
|
267
|
+
if (!conv) throw new Error(`Conversation not found: ${id}`);
|
|
268
|
+
if (patch.title !== void 0) conv.title = patch.title;
|
|
269
|
+
conv.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
270
|
+
},
|
|
271
|
+
async delete(id) {
|
|
272
|
+
conversations.delete(id);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/contracts/active-work.ts
|
|
278
|
+
function createActiveWorkTracker() {
|
|
279
|
+
const works = /* @__PURE__ */ new Map();
|
|
280
|
+
return {
|
|
281
|
+
register(work) {
|
|
282
|
+
works.set(work.id, work);
|
|
283
|
+
},
|
|
284
|
+
unregister(id) {
|
|
285
|
+
works.delete(id);
|
|
286
|
+
},
|
|
287
|
+
list() {
|
|
288
|
+
return Array.from(works.values());
|
|
289
|
+
},
|
|
290
|
+
listByProvider(providerId) {
|
|
291
|
+
return Array.from(works.values()).filter((w) => w.providerId === providerId);
|
|
292
|
+
},
|
|
293
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
294
|
+
async cancelAll(_reason) {
|
|
295
|
+
for (const work of works.values()) {
|
|
296
|
+
work.abort();
|
|
297
|
+
}
|
|
298
|
+
works.clear();
|
|
299
|
+
},
|
|
300
|
+
async cancelByProvider(providerId) {
|
|
301
|
+
for (const [id, work] of works.entries()) {
|
|
302
|
+
if (work.providerId === providerId) {
|
|
303
|
+
work.abort();
|
|
304
|
+
works.delete(id);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/contracts/readiness.ts
|
|
312
|
+
var EMPTY_CAPABILITIES = {
|
|
313
|
+
streamingText: false,
|
|
314
|
+
reasoning: false,
|
|
315
|
+
toolCalling: false,
|
|
316
|
+
parallelToolCalls: false,
|
|
317
|
+
toolArgsStreaming: false,
|
|
318
|
+
abort: false,
|
|
319
|
+
hitl: false,
|
|
320
|
+
conversations: false,
|
|
321
|
+
sessionResources: false,
|
|
322
|
+
resourcePicker: false,
|
|
323
|
+
embeddings: false,
|
|
324
|
+
rerank: false
|
|
325
|
+
};
|
|
326
|
+
function createDisabledState() {
|
|
327
|
+
return {
|
|
328
|
+
enabled: false,
|
|
329
|
+
activeProviderId: null,
|
|
330
|
+
activeProviderKind: null,
|
|
331
|
+
ready: false,
|
|
332
|
+
capabilities: { ...EMPTY_CAPABILITIES },
|
|
333
|
+
source: "manual-disabled"
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/contracts/secrets.ts
|
|
338
|
+
function createMemorySecretStore() {
|
|
339
|
+
const secrets = /* @__PURE__ */ new Map();
|
|
340
|
+
let counter = 0;
|
|
341
|
+
return {
|
|
342
|
+
async put(input) {
|
|
343
|
+
counter++;
|
|
344
|
+
const id = `secret-${counter}`;
|
|
345
|
+
const ref = { id, providerInstanceId: input.providerInstanceId };
|
|
346
|
+
const now = Date.now();
|
|
347
|
+
secrets.set(id, {
|
|
348
|
+
value: input.value,
|
|
349
|
+
descriptor: {
|
|
350
|
+
id,
|
|
351
|
+
providerInstanceId: input.providerInstanceId,
|
|
352
|
+
label: input.label,
|
|
353
|
+
maskedValue: input.value.slice(0, 4) + "****",
|
|
354
|
+
createdAt: now,
|
|
355
|
+
updatedAt: now
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
return ref;
|
|
359
|
+
},
|
|
360
|
+
async get(ref) {
|
|
361
|
+
const entry = secrets.get(ref.id);
|
|
362
|
+
if (!entry) throw new Error(`Secret not found: ${ref.id}`);
|
|
363
|
+
return entry.value;
|
|
364
|
+
},
|
|
365
|
+
async delete(ref) {
|
|
366
|
+
secrets.delete(ref.id);
|
|
367
|
+
},
|
|
368
|
+
async describe(ref) {
|
|
369
|
+
const entry = secrets.get(ref.id);
|
|
370
|
+
if (!entry) throw new Error(`Secret not found: ${ref.id}`);
|
|
371
|
+
return entry.descriptor;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/contracts/session.ts
|
|
377
|
+
function createSessionManager(workTracker) {
|
|
378
|
+
let activeProvider = null;
|
|
379
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
380
|
+
function emit(event) {
|
|
381
|
+
for (const listener of listeners) {
|
|
382
|
+
listener(event);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
getActiveProvider() {
|
|
387
|
+
return activeProvider;
|
|
388
|
+
},
|
|
389
|
+
getActiveDescriptor() {
|
|
390
|
+
if (!activeProvider) return null;
|
|
391
|
+
return {
|
|
392
|
+
id: activeProvider.id,
|
|
393
|
+
kind: activeProvider.kind,
|
|
394
|
+
displayName: activeProvider.displayName,
|
|
395
|
+
capabilities: activeProvider.capabilities
|
|
396
|
+
};
|
|
397
|
+
},
|
|
398
|
+
getRuntimeState() {
|
|
399
|
+
if (!activeProvider) return createDisabledState();
|
|
400
|
+
return {
|
|
401
|
+
enabled: true,
|
|
402
|
+
activeProviderId: activeProvider.id,
|
|
403
|
+
activeProviderKind: activeProvider.kind,
|
|
404
|
+
ready: true,
|
|
405
|
+
capabilities: activeProvider.capabilities,
|
|
406
|
+
source: "runtime-provider"
|
|
407
|
+
};
|
|
408
|
+
},
|
|
409
|
+
async switchProvider(config, options) {
|
|
410
|
+
const previousId = activeProvider?.id ?? null;
|
|
411
|
+
if (options?.ifBusy === "cancel-active-work") {
|
|
412
|
+
await workTracker.cancelAll("provider-switch");
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
await config.provider.ensureReady();
|
|
416
|
+
} catch (error) {
|
|
417
|
+
const message = error instanceof Error ? error.message : "Provider readiness failed";
|
|
418
|
+
return {
|
|
419
|
+
success: false,
|
|
420
|
+
previousProviderId: previousId ?? void 0,
|
|
421
|
+
error: message
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const oldProvider = activeProvider;
|
|
425
|
+
activeProvider = config.provider;
|
|
426
|
+
if (oldProvider?.dispose) {
|
|
427
|
+
try {
|
|
428
|
+
await oldProvider.dispose();
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
emit({
|
|
433
|
+
previousProviderId: previousId,
|
|
434
|
+
newProviderId: config.provider.id,
|
|
435
|
+
capabilities: config.provider.capabilities
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
success: true,
|
|
439
|
+
previousProviderId: previousId ?? void 0,
|
|
440
|
+
newProviderId: config.provider.id
|
|
441
|
+
};
|
|
442
|
+
},
|
|
443
|
+
async cancelAll(reason) {
|
|
444
|
+
await workTracker.cancelAll(reason);
|
|
445
|
+
},
|
|
446
|
+
onProviderChanged(listener) {
|
|
447
|
+
listeners.add(listener);
|
|
448
|
+
return () => listeners.delete(listener);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/contracts/task-pipeline.ts
|
|
454
|
+
function createTaskPipeline(getProvider) {
|
|
455
|
+
let formatterQueue = Promise.resolve();
|
|
456
|
+
return {
|
|
457
|
+
async *executeWithFormatter(input) {
|
|
458
|
+
const provider = getProvider();
|
|
459
|
+
if (!provider) {
|
|
460
|
+
yield { type: "error", error: "No active provider" };
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
yield { type: "start" };
|
|
464
|
+
let resultText = "";
|
|
465
|
+
const contentStream = provider.chatStream({
|
|
466
|
+
agentId: input.contentAgentId,
|
|
467
|
+
messages: input.contentMessages,
|
|
468
|
+
signal: input.signal
|
|
469
|
+
});
|
|
470
|
+
for await (const event of contentStream) {
|
|
471
|
+
if (input.signal?.aborted) {
|
|
472
|
+
yield { type: "cancelled" };
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (event.type === "text") {
|
|
476
|
+
resultText += event.content;
|
|
477
|
+
}
|
|
478
|
+
if (event.type === "error") {
|
|
479
|
+
yield event;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
yield event;
|
|
483
|
+
}
|
|
484
|
+
if (!resultText || input.signal?.aborted) {
|
|
485
|
+
if (input.signal?.aborted) {
|
|
486
|
+
yield { type: "cancelled" };
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const previousQueue = formatterQueue;
|
|
491
|
+
let releaseSlotFn;
|
|
492
|
+
const currentSlot = new Promise((resolve) => {
|
|
493
|
+
releaseSlotFn = resolve;
|
|
494
|
+
});
|
|
495
|
+
formatterQueue = previousQueue.catch(() => void 0).then(() => currentSlot);
|
|
496
|
+
await previousQueue.catch(() => void 0);
|
|
497
|
+
try {
|
|
498
|
+
if (input.signal?.aborted) {
|
|
499
|
+
yield { type: "cancelled" };
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const formatterPrompt = buildFormatterPrompt(
|
|
503
|
+
input.contentMessages[input.contentMessages.length - 1]?.content ?? "",
|
|
504
|
+
resultText,
|
|
505
|
+
input.outputFormat
|
|
506
|
+
);
|
|
507
|
+
const formatterStream = provider.chatStream({
|
|
508
|
+
agentId: input.formatterConfig.id,
|
|
509
|
+
messages: [
|
|
510
|
+
{ role: "system", content: input.formatterConfig.systemPrompt },
|
|
511
|
+
{ role: "user", content: formatterPrompt }
|
|
512
|
+
],
|
|
513
|
+
tools: input.outputTools,
|
|
514
|
+
signal: input.signal
|
|
515
|
+
});
|
|
516
|
+
for await (const event of formatterStream) {
|
|
517
|
+
if (input.signal?.aborted) {
|
|
518
|
+
yield { type: "cancelled" };
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
yield event;
|
|
522
|
+
}
|
|
523
|
+
yield { type: "done", conversationId: "" };
|
|
524
|
+
} finally {
|
|
525
|
+
releaseSlotFn?.();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function buildFormatterPrompt(userRequest, content, outputFormat) {
|
|
531
|
+
let request = userRequest;
|
|
532
|
+
if (outputFormat && outputFormat !== "auto") {
|
|
533
|
+
const formatMap = {
|
|
534
|
+
paragraph: "paragraph format",
|
|
535
|
+
list: "list format",
|
|
536
|
+
table: "table format",
|
|
537
|
+
code: "code block format",
|
|
538
|
+
quote: "blockquote format"
|
|
539
|
+
};
|
|
540
|
+
const hint = formatMap[outputFormat] ?? outputFormat;
|
|
541
|
+
request = request ? `${request} (use ${hint})` : `Use ${hint}`;
|
|
542
|
+
}
|
|
543
|
+
return `<user_request>
|
|
544
|
+
${request || "No specific request"}
|
|
545
|
+
</user_request>
|
|
546
|
+
|
|
547
|
+
<original_content>
|
|
548
|
+
${content}
|
|
549
|
+
</original_content>`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/contracts/embedding.ts
|
|
553
|
+
function createEmbeddingSession() {
|
|
554
|
+
let embeddingProvider = null;
|
|
555
|
+
let rerankProvider = null;
|
|
556
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
557
|
+
function emit(event) {
|
|
558
|
+
for (const listener of listeners) {
|
|
559
|
+
listener(event);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
getEmbeddingProvider() {
|
|
564
|
+
return embeddingProvider;
|
|
565
|
+
},
|
|
566
|
+
getRerankProvider() {
|
|
567
|
+
return rerankProvider;
|
|
568
|
+
},
|
|
569
|
+
async setEmbeddingProvider(provider) {
|
|
570
|
+
const prev = embeddingProvider;
|
|
571
|
+
await provider.ensureReady();
|
|
572
|
+
const modelChanged = !prev || prev.model !== provider.model || prev.dimensions !== provider.dimensions;
|
|
573
|
+
if (prev?.dispose) {
|
|
574
|
+
await prev.dispose().catch(() => {
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
embeddingProvider = provider;
|
|
578
|
+
if (modelChanged) {
|
|
579
|
+
const event = {
|
|
580
|
+
previousModel: prev?.model ?? null,
|
|
581
|
+
previousDimensions: prev?.dimensions ?? null,
|
|
582
|
+
newModel: provider.model,
|
|
583
|
+
newDimensions: provider.dimensions
|
|
584
|
+
};
|
|
585
|
+
emit(event);
|
|
586
|
+
return event;
|
|
587
|
+
}
|
|
588
|
+
return null;
|
|
589
|
+
},
|
|
590
|
+
async setRerankProvider(provider) {
|
|
591
|
+
const prev = rerankProvider;
|
|
592
|
+
await provider.ensureReady();
|
|
593
|
+
if (prev?.dispose) {
|
|
594
|
+
await prev.dispose().catch(() => {
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
rerankProvider = provider;
|
|
598
|
+
},
|
|
599
|
+
async clearEmbeddingProvider() {
|
|
600
|
+
const prev = embeddingProvider;
|
|
601
|
+
if (!prev) return null;
|
|
602
|
+
if (prev.dispose) {
|
|
603
|
+
await prev.dispose().catch(() => {
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
embeddingProvider = null;
|
|
607
|
+
const event = {
|
|
608
|
+
previousModel: prev.model,
|
|
609
|
+
previousDimensions: prev.dimensions,
|
|
610
|
+
newModel: "",
|
|
611
|
+
newDimensions: 0
|
|
612
|
+
};
|
|
613
|
+
emit(event);
|
|
614
|
+
return event;
|
|
615
|
+
},
|
|
616
|
+
async clearRerankProvider() {
|
|
617
|
+
const prev = rerankProvider;
|
|
618
|
+
if (prev?.dispose) {
|
|
619
|
+
await prev.dispose().catch(() => {
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
rerankProvider = null;
|
|
623
|
+
},
|
|
624
|
+
onModelChange(listener) {
|
|
625
|
+
listeners.add(listener);
|
|
626
|
+
return () => listeners.delete(listener);
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// src/contracts/bridge.ts
|
|
632
|
+
function resolveAiFeaturesFromRuntime(preference, runtimeState, sanqianInstalled) {
|
|
633
|
+
if (preference === "enabled") return true;
|
|
634
|
+
if (preference === "disabled") return false;
|
|
635
|
+
return runtimeState.ready || sanqianInstalled;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/contracts/bridge-impl.ts
|
|
639
|
+
function createChatRuntimeBridge(config) {
|
|
640
|
+
const { sessionManager, conversationStore, permissionGate } = config;
|
|
641
|
+
const activeStreams = /* @__PURE__ */ new Map();
|
|
642
|
+
return {
|
|
643
|
+
getActiveStreams() {
|
|
644
|
+
return activeStreams;
|
|
645
|
+
},
|
|
646
|
+
async ensureReady() {
|
|
647
|
+
const provider = sessionManager.getActiveProvider();
|
|
648
|
+
if (!provider) throw new Error("No active provider");
|
|
649
|
+
await provider.ensureReady();
|
|
650
|
+
},
|
|
651
|
+
isConnected() {
|
|
652
|
+
return sessionManager.getActiveProvider() !== null;
|
|
653
|
+
},
|
|
654
|
+
getRuntimeState() {
|
|
655
|
+
return sessionManager.getRuntimeState();
|
|
656
|
+
},
|
|
657
|
+
getCapabilities() {
|
|
658
|
+
const provider = sessionManager.getActiveProvider();
|
|
659
|
+
return provider?.capabilities ?? EMPTY_CAPABILITIES;
|
|
660
|
+
},
|
|
661
|
+
async *chatStream(input) {
|
|
662
|
+
const provider = sessionManager.getActiveProvider();
|
|
663
|
+
if (!provider) {
|
|
664
|
+
yield { type: "error", error: "No active provider" };
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const streamId = `stream-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
668
|
+
const abortController = new AbortController();
|
|
669
|
+
const streamEntry = {
|
|
670
|
+
streamId,
|
|
671
|
+
runId: null,
|
|
672
|
+
abortController,
|
|
673
|
+
cancelled: false
|
|
674
|
+
};
|
|
675
|
+
activeStreams.set(streamId, streamEntry);
|
|
676
|
+
if (input.signal) {
|
|
677
|
+
input.signal.addEventListener("abort", () => {
|
|
678
|
+
streamEntry.cancelled = true;
|
|
679
|
+
abortController.abort();
|
|
680
|
+
}, { once: true });
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
const stream = provider.chatStream({
|
|
684
|
+
...input,
|
|
685
|
+
signal: abortController.signal
|
|
686
|
+
});
|
|
687
|
+
for await (const event of stream) {
|
|
688
|
+
if (streamEntry.cancelled) {
|
|
689
|
+
yield { type: "cancelled", runId: streamEntry.runId ?? void 0 };
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (event.type === "start" && "runId" in event && event.runId) {
|
|
693
|
+
streamEntry.runId = event.runId;
|
|
694
|
+
}
|
|
695
|
+
yield event;
|
|
696
|
+
}
|
|
697
|
+
} catch (error) {
|
|
698
|
+
if (!streamEntry.cancelled) {
|
|
699
|
+
yield { type: "error", error: error instanceof Error ? error.message : "Stream error" };
|
|
700
|
+
}
|
|
701
|
+
} finally {
|
|
702
|
+
activeStreams.delete(streamId);
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
async cancelStream(input) {
|
|
706
|
+
if (input?.streamId) {
|
|
707
|
+
const stream = activeStreams.get(input.streamId);
|
|
708
|
+
if (stream) {
|
|
709
|
+
stream.cancelled = true;
|
|
710
|
+
stream.abortController.abort();
|
|
711
|
+
const provider = sessionManager.getActiveProvider();
|
|
712
|
+
if (provider?.cancel && stream.runId) {
|
|
713
|
+
await provider.cancel(stream.runId);
|
|
714
|
+
}
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (input?.runId) {
|
|
719
|
+
const provider = sessionManager.getActiveProvider();
|
|
720
|
+
if (provider?.cancel) {
|
|
721
|
+
await provider.cancel(input.runId);
|
|
722
|
+
}
|
|
723
|
+
for (const stream of activeStreams.values()) {
|
|
724
|
+
if (stream.runId === input.runId) {
|
|
725
|
+
stream.cancelled = true;
|
|
726
|
+
stream.abortController.abort();
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
async sendPermissionResponse(input) {
|
|
733
|
+
if (permissionGate) {
|
|
734
|
+
permissionGate.resolvePermission(input.runId, input.decision);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const provider = sessionManager.getActiveProvider();
|
|
738
|
+
if (provider?.sendPermissionResponse) {
|
|
739
|
+
await provider.sendPermissionResponse(input);
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
async listConversations(input) {
|
|
743
|
+
return conversationStore.list(input);
|
|
744
|
+
},
|
|
745
|
+
async getConversation(id, input) {
|
|
746
|
+
return conversationStore.get(id, input);
|
|
747
|
+
},
|
|
748
|
+
async deleteConversation(id) {
|
|
749
|
+
return conversationStore.delete(id);
|
|
750
|
+
}
|
|
751
|
+
// Resource picker -- capability-gated, not implemented for non-Sanqian
|
|
752
|
+
// These methods are optional on ChatRuntimeBridge
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/contracts/output-operations.ts
|
|
757
|
+
function validateOutputContent(type, content) {
|
|
758
|
+
if (content === null || content === void 0 || typeof content !== "object") {
|
|
759
|
+
return `${type}: content must be an object`;
|
|
760
|
+
}
|
|
761
|
+
const c = content;
|
|
762
|
+
switch (type) {
|
|
763
|
+
case "paragraph":
|
|
764
|
+
if (!Array.isArray(c.paragraphs)) return "paragraph: paragraphs must be an array";
|
|
765
|
+
break;
|
|
766
|
+
case "list":
|
|
767
|
+
if (!Array.isArray(c.items)) return "list: items must be an array";
|
|
768
|
+
break;
|
|
769
|
+
case "table":
|
|
770
|
+
if (!Array.isArray(c.headers)) return "table: headers must be an array";
|
|
771
|
+
if (!Array.isArray(c.rows)) return "table: rows must be an array";
|
|
772
|
+
break;
|
|
773
|
+
case "heading":
|
|
774
|
+
if (typeof c.text !== "string") return "heading: text must be a string";
|
|
775
|
+
break;
|
|
776
|
+
case "codeBlock":
|
|
777
|
+
if (typeof c.code !== "string") return "codeBlock: code must be a string";
|
|
778
|
+
break;
|
|
779
|
+
case "blockquote":
|
|
780
|
+
if (typeof c.text !== "string") return "blockquote: text must be a string";
|
|
781
|
+
break;
|
|
782
|
+
case "html":
|
|
783
|
+
if (typeof c.html !== "string") return "html: html must be a string";
|
|
784
|
+
break;
|
|
785
|
+
case "noteRef":
|
|
786
|
+
if (typeof c.noteTitle !== "string") return "noteRef: noteTitle must be a string";
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
function createOutputOperationsManager() {
|
|
792
|
+
const pending = /* @__PURE__ */ new Map();
|
|
793
|
+
return {
|
|
794
|
+
initTaskOutput(taskId, context) {
|
|
795
|
+
pending.set(taskId, { context, operations: [] });
|
|
796
|
+
},
|
|
797
|
+
queueOp(taskId, type, content) {
|
|
798
|
+
const entry = pending.get(taskId);
|
|
799
|
+
if (!entry) {
|
|
800
|
+
return { success: false, error: "No pending context for task" };
|
|
801
|
+
}
|
|
802
|
+
const validationError = validateOutputContent(type, content);
|
|
803
|
+
if (validationError) {
|
|
804
|
+
return { success: false, error: validationError };
|
|
805
|
+
}
|
|
806
|
+
entry.operations.push({ type, content });
|
|
807
|
+
return { success: true };
|
|
808
|
+
},
|
|
809
|
+
getTaskOutput(taskId) {
|
|
810
|
+
return pending.get(taskId) ?? null;
|
|
811
|
+
},
|
|
812
|
+
commitTaskOutput(taskId) {
|
|
813
|
+
const entry = pending.get(taskId);
|
|
814
|
+
if (!entry || entry.operations.length === 0) {
|
|
815
|
+
pending.delete(taskId);
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
const data = {
|
|
819
|
+
taskId,
|
|
820
|
+
context: entry.context,
|
|
821
|
+
operations: [...entry.operations]
|
|
822
|
+
};
|
|
823
|
+
pending.delete(taskId);
|
|
824
|
+
return data;
|
|
825
|
+
},
|
|
826
|
+
clearTaskOutput(taskId) {
|
|
827
|
+
pending.delete(taskId);
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/providers/sanqian/event-converter.ts
|
|
833
|
+
function convertSdkStreamEvent(event) {
|
|
834
|
+
switch (event.type) {
|
|
835
|
+
case "start":
|
|
836
|
+
return {
|
|
837
|
+
type: "start",
|
|
838
|
+
runId: event.run_id,
|
|
839
|
+
conversationId: event.conversationId
|
|
840
|
+
};
|
|
841
|
+
case "text":
|
|
842
|
+
return {
|
|
843
|
+
type: "text",
|
|
844
|
+
content: event.content ?? ""
|
|
845
|
+
};
|
|
846
|
+
case "thinking":
|
|
847
|
+
return {
|
|
848
|
+
type: "thinking",
|
|
849
|
+
content: event.content ?? ""
|
|
850
|
+
};
|
|
851
|
+
case "tool_call":
|
|
852
|
+
if (!event.tool_call) return null;
|
|
853
|
+
return {
|
|
854
|
+
type: "tool_call",
|
|
855
|
+
toolCall: {
|
|
856
|
+
id: event.tool_call.id,
|
|
857
|
+
name: event.tool_call.function.name,
|
|
858
|
+
arguments: event.tool_call.function.arguments
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
case "tool_args_chunk":
|
|
862
|
+
return {
|
|
863
|
+
type: "tool_args_chunk",
|
|
864
|
+
toolCallId: event.tool_call_id ?? "",
|
|
865
|
+
toolName: event.tool_name ?? "",
|
|
866
|
+
chunk: event.chunk ?? ""
|
|
867
|
+
};
|
|
868
|
+
case "tool_args":
|
|
869
|
+
return {
|
|
870
|
+
type: "tool_args",
|
|
871
|
+
toolCallId: event.tool_call_id ?? "",
|
|
872
|
+
toolName: event.tool_name ?? "",
|
|
873
|
+
args: event.args ?? {}
|
|
874
|
+
};
|
|
875
|
+
case "tool_result":
|
|
876
|
+
return {
|
|
877
|
+
type: "tool_result",
|
|
878
|
+
toolCallId: event.tool_call_id ?? "",
|
|
879
|
+
result: event.result,
|
|
880
|
+
success: event.success,
|
|
881
|
+
error: event.error,
|
|
882
|
+
status: event.status,
|
|
883
|
+
actionRequired: event.action_required,
|
|
884
|
+
settingsTab: event.settings_tab,
|
|
885
|
+
settingsSubTab: event.settings_sub_tab,
|
|
886
|
+
commandExitCode: event.command_exit_code,
|
|
887
|
+
durationMs: event.duration_ms,
|
|
888
|
+
sandboxed: event.sandboxed,
|
|
889
|
+
timedOut: event.timed_out,
|
|
890
|
+
truncated: event.truncated,
|
|
891
|
+
stdoutPath: event.stdout_path,
|
|
892
|
+
stderrPath: event.stderr_path,
|
|
893
|
+
presentation: event.presentation
|
|
894
|
+
};
|
|
895
|
+
case "done":
|
|
896
|
+
return {
|
|
897
|
+
type: "done",
|
|
898
|
+
conversationId: event.conversationId ?? "",
|
|
899
|
+
title: event.title
|
|
900
|
+
};
|
|
901
|
+
case "error":
|
|
902
|
+
return {
|
|
903
|
+
type: "error",
|
|
904
|
+
error: event.error ?? "Unknown error"
|
|
905
|
+
};
|
|
906
|
+
case "cancelled":
|
|
907
|
+
return {
|
|
908
|
+
type: "cancelled",
|
|
909
|
+
runId: event.run_id
|
|
910
|
+
};
|
|
911
|
+
case "interrupt":
|
|
912
|
+
return {
|
|
913
|
+
type: "interrupt",
|
|
914
|
+
runId: event.run_id,
|
|
915
|
+
interrupt: {
|
|
916
|
+
type: event.interrupt_type ?? "",
|
|
917
|
+
payload: event.interrupt_payload
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
default:
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/providers/sanqian/agent-id-resolver.ts
|
|
926
|
+
function createSanqianAgentIdResolver(appName) {
|
|
927
|
+
const mappings = /* @__PURE__ */ new Map();
|
|
928
|
+
const reverseMappings = /* @__PURE__ */ new Map();
|
|
929
|
+
const prefix = `${appName}:`;
|
|
930
|
+
return {
|
|
931
|
+
setMapping(shortId, registeredId) {
|
|
932
|
+
mappings.set(shortId, registeredId);
|
|
933
|
+
reverseMappings.set(registeredId, shortId);
|
|
934
|
+
},
|
|
935
|
+
clearMappings() {
|
|
936
|
+
mappings.clear();
|
|
937
|
+
reverseMappings.clear();
|
|
938
|
+
},
|
|
939
|
+
toRuntimeAgentId(input) {
|
|
940
|
+
if (!input.includes(":")) return input;
|
|
941
|
+
const short = reverseMappings.get(input);
|
|
942
|
+
if (short) return short;
|
|
943
|
+
if (input.startsWith(prefix)) {
|
|
944
|
+
return input.slice(prefix.length);
|
|
945
|
+
}
|
|
946
|
+
return input;
|
|
947
|
+
},
|
|
948
|
+
async toProviderAgentId(shortId) {
|
|
949
|
+
const registered = mappings.get(shortId);
|
|
950
|
+
if (registered) return registered;
|
|
951
|
+
if (shortId.includes(":")) return shortId;
|
|
952
|
+
return `${prefix}${shortId}`;
|
|
953
|
+
},
|
|
954
|
+
isProviderManagedId(input) {
|
|
955
|
+
return input.includes(":");
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/providers/sanqian/provider.ts
|
|
961
|
+
var SANQIAN_CAPABILITIES = {
|
|
962
|
+
streamingText: true,
|
|
963
|
+
reasoning: true,
|
|
964
|
+
toolCalling: true,
|
|
965
|
+
parallelToolCalls: true,
|
|
966
|
+
toolArgsStreaming: true,
|
|
967
|
+
abort: true,
|
|
968
|
+
hitl: true,
|
|
969
|
+
conversations: true,
|
|
970
|
+
sessionResources: true,
|
|
971
|
+
resourcePicker: true,
|
|
972
|
+
embeddings: true,
|
|
973
|
+
rerank: true
|
|
974
|
+
};
|
|
975
|
+
var SanqianProvider = class {
|
|
976
|
+
id;
|
|
977
|
+
kind = "sanqian-sdk";
|
|
978
|
+
displayName;
|
|
979
|
+
capabilities = SANQIAN_CAPABILITIES;
|
|
980
|
+
sdk;
|
|
981
|
+
appName;
|
|
982
|
+
agentConfigs;
|
|
983
|
+
agentIdResolver;
|
|
984
|
+
disposed = false;
|
|
985
|
+
ready = false;
|
|
986
|
+
connectionStatus;
|
|
987
|
+
connectionError;
|
|
988
|
+
connectionErrorCode;
|
|
989
|
+
connectionLastChangedAt = Date.now();
|
|
990
|
+
connectionListeners = /* @__PURE__ */ new Set();
|
|
991
|
+
handleSdkConnected = () => {
|
|
992
|
+
this.updateConnection("connected");
|
|
993
|
+
};
|
|
994
|
+
handleSdkDisconnected = () => {
|
|
995
|
+
const shouldReconnect = this.ready || this.connectionStatus === "connected" || this.connectionStatus === "reconnecting";
|
|
996
|
+
this.ready = false;
|
|
997
|
+
this.updateConnection(!this.disposed && shouldReconnect ? "reconnecting" : "disconnected");
|
|
998
|
+
};
|
|
999
|
+
handleSdkError = (error) => {
|
|
1000
|
+
if (!this.sdk.isConnected()) {
|
|
1001
|
+
this.ready = false;
|
|
1002
|
+
}
|
|
1003
|
+
this.updateConnection("error", normalizeConnectionError(error));
|
|
1004
|
+
};
|
|
1005
|
+
constructor(config) {
|
|
1006
|
+
this.id = config.id ?? "sanqian";
|
|
1007
|
+
this.displayName = config.displayName ?? "Sanqian";
|
|
1008
|
+
this.sdk = config.sdk;
|
|
1009
|
+
this.appName = config.appName;
|
|
1010
|
+
this.agentConfigs = config.agents ?? [];
|
|
1011
|
+
this.agentIdResolver = createSanqianAgentIdResolver(config.appName);
|
|
1012
|
+
this.connectionStatus = this.sdk.isConnected() ? "connected" : "disconnected";
|
|
1013
|
+
this.registerConnectionEvents();
|
|
1014
|
+
}
|
|
1015
|
+
// --- Lifecycle ---
|
|
1016
|
+
async ensureReady() {
|
|
1017
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
1018
|
+
if (this.ready && this.sdk.isConnected()) return;
|
|
1019
|
+
if (this.ready && !this.sdk.isConnected()) {
|
|
1020
|
+
this.ready = false;
|
|
1021
|
+
}
|
|
1022
|
+
this.updateConnection(this.sdk.isConnected() ? "connected" : "connecting");
|
|
1023
|
+
try {
|
|
1024
|
+
await this.sdk.ensureReady();
|
|
1025
|
+
this.updateConnection("connected");
|
|
1026
|
+
await this.syncAgents();
|
|
1027
|
+
this.ready = true;
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
this.ready = false;
|
|
1030
|
+
this.updateConnection("error", normalizeConnectionError(error));
|
|
1031
|
+
throw error;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
async dispose() {
|
|
1035
|
+
if (this.disposed) return;
|
|
1036
|
+
this.disposed = true;
|
|
1037
|
+
this.ready = false;
|
|
1038
|
+
this.agentIdResolver.clearMappings();
|
|
1039
|
+
this.unregisterConnectionEvents();
|
|
1040
|
+
this.updateConnection("disconnected");
|
|
1041
|
+
this.sdk.removeAllListeners();
|
|
1042
|
+
await this.sdk.disconnect();
|
|
1043
|
+
}
|
|
1044
|
+
isDisposed() {
|
|
1045
|
+
return this.disposed;
|
|
1046
|
+
}
|
|
1047
|
+
// --- Agent Sync ---
|
|
1048
|
+
async syncAgents() {
|
|
1049
|
+
for (const config of this.agentConfigs) {
|
|
1050
|
+
const sdkConfig = {
|
|
1051
|
+
agent_id: config.id,
|
|
1052
|
+
name: config.name,
|
|
1053
|
+
description: config.description,
|
|
1054
|
+
system_prompt: config.systemPrompt,
|
|
1055
|
+
tools: config.tools
|
|
1056
|
+
};
|
|
1057
|
+
const result = await this.sdk.createAgent(sdkConfig);
|
|
1058
|
+
this.agentIdResolver.setMapping(config.id, result.agent_id);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Get the registered Sanqian agent ID for a runtime agent ID.
|
|
1063
|
+
*/
|
|
1064
|
+
async resolveAgentId(shortId) {
|
|
1065
|
+
return this.agentIdResolver.toProviderAgentId(shortId);
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Get the runtime agent ID for a registered Sanqian agent ID.
|
|
1069
|
+
*/
|
|
1070
|
+
normalizeAgentId(registeredId) {
|
|
1071
|
+
return this.agentIdResolver.toRuntimeAgentId(registeredId);
|
|
1072
|
+
}
|
|
1073
|
+
// --- Chat ---
|
|
1074
|
+
async chat(input) {
|
|
1075
|
+
this.ensureNotDisposed();
|
|
1076
|
+
const agentId = await this.resolveAgentId(input.agentId);
|
|
1077
|
+
const messages = input.messages.map((m) => ({ role: m.role, content: m.content }));
|
|
1078
|
+
const response = await this.sdk.chat(agentId, messages, {
|
|
1079
|
+
conversationId: input.conversationId ?? void 0
|
|
1080
|
+
});
|
|
1081
|
+
return {
|
|
1082
|
+
content: response.message.content,
|
|
1083
|
+
conversationId: response.conversationId || void 0,
|
|
1084
|
+
usage: response.usage ? {
|
|
1085
|
+
promptTokens: response.usage.prompt_tokens,
|
|
1086
|
+
completionTokens: response.usage.completion_tokens,
|
|
1087
|
+
totalTokens: response.usage.total_tokens
|
|
1088
|
+
} : void 0
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
async *chatStream(input) {
|
|
1092
|
+
this.ensureNotDisposed();
|
|
1093
|
+
const agentId = await this.resolveAgentId(input.agentId);
|
|
1094
|
+
const messages = input.messages.map((m) => ({ role: m.role, content: m.content }));
|
|
1095
|
+
const stream = this.sdk.chatStream(agentId, messages, {
|
|
1096
|
+
conversationId: input.conversationId ?? void 0,
|
|
1097
|
+
signal: input.signal,
|
|
1098
|
+
attachedResources: input.resources?.map((r) => `${r.providerId}:${r.resourceId}`),
|
|
1099
|
+
sessionResources: input.sessionResources?.map((r) => r.id)
|
|
1100
|
+
});
|
|
1101
|
+
for await (const sdkEvent of stream) {
|
|
1102
|
+
if (input.signal?.aborted) {
|
|
1103
|
+
yield { type: "cancelled" };
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const runtimeEvent = convertSdkStreamEvent(sdkEvent);
|
|
1107
|
+
if (runtimeEvent) {
|
|
1108
|
+
yield runtimeEvent;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
// --- Cancel / HITL ---
|
|
1113
|
+
async cancel(runId) {
|
|
1114
|
+
this.ensureNotDisposed();
|
|
1115
|
+
this.sdk.cancelRun(runId);
|
|
1116
|
+
}
|
|
1117
|
+
async sendPermissionResponse(input) {
|
|
1118
|
+
this.ensureNotDisposed();
|
|
1119
|
+
this.sdk.sendHitlResponse(input.runId, input.response ?? {
|
|
1120
|
+
approved: input.decision === "approve",
|
|
1121
|
+
cancelled: input.decision === "cancel"
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
// --- Conversations ---
|
|
1125
|
+
async listConversations(options) {
|
|
1126
|
+
this.ensureNotDisposed();
|
|
1127
|
+
return this.sdk.listConversations({
|
|
1128
|
+
agentId: options?.agentId,
|
|
1129
|
+
limit: options?.limit,
|
|
1130
|
+
offset: options?.offset
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
async getConversation(id, options) {
|
|
1134
|
+
this.ensureNotDisposed();
|
|
1135
|
+
return this.sdk.getConversation(id, options);
|
|
1136
|
+
}
|
|
1137
|
+
async deleteConversation(id) {
|
|
1138
|
+
this.ensureNotDisposed();
|
|
1139
|
+
return this.sdk.deleteConversation(id);
|
|
1140
|
+
}
|
|
1141
|
+
// --- Session Resources ---
|
|
1142
|
+
getSessionResources() {
|
|
1143
|
+
return this.sdk.getSessionResources();
|
|
1144
|
+
}
|
|
1145
|
+
async pushResource(resource) {
|
|
1146
|
+
this.ensureNotDisposed();
|
|
1147
|
+
return this.sdk.pushResource(resource);
|
|
1148
|
+
}
|
|
1149
|
+
async removeResource(resourceId) {
|
|
1150
|
+
this.ensureNotDisposed();
|
|
1151
|
+
return this.sdk.removeResource(resourceId);
|
|
1152
|
+
}
|
|
1153
|
+
// --- Embedding / Rerank ---
|
|
1154
|
+
async getEmbeddingConfig() {
|
|
1155
|
+
try {
|
|
1156
|
+
return await this.sdk.getEmbeddingConfig();
|
|
1157
|
+
} catch {
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
async getRerankConfig() {
|
|
1162
|
+
try {
|
|
1163
|
+
return await this.sdk.getRerankConfig();
|
|
1164
|
+
} catch {
|
|
1165
|
+
return null;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
// --- Connection lifecycle ---
|
|
1169
|
+
acquireReconnect() {
|
|
1170
|
+
this.sdk.acquireReconnect();
|
|
1171
|
+
}
|
|
1172
|
+
releaseReconnect() {
|
|
1173
|
+
this.sdk.releaseReconnect();
|
|
1174
|
+
}
|
|
1175
|
+
// --- Connection state ---
|
|
1176
|
+
isConnected() {
|
|
1177
|
+
return this.sdk.isConnected();
|
|
1178
|
+
}
|
|
1179
|
+
getConnectionSnapshot() {
|
|
1180
|
+
return {
|
|
1181
|
+
status: this.connectionStatus,
|
|
1182
|
+
isConnected: this.sdk.isConnected(),
|
|
1183
|
+
error: this.connectionError,
|
|
1184
|
+
errorCode: this.connectionErrorCode,
|
|
1185
|
+
lastChangedAt: this.connectionLastChangedAt
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
onConnectionChange(callback) {
|
|
1189
|
+
this.connectionListeners.add(callback);
|
|
1190
|
+
callback(this.getConnectionSnapshot());
|
|
1191
|
+
return () => {
|
|
1192
|
+
this.connectionListeners.delete(callback);
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
// --- Events (Sanqian-specific, not on RuntimeProvider interface) ---
|
|
1196
|
+
on(event, handler) {
|
|
1197
|
+
this.sdk.on(event, handler);
|
|
1198
|
+
}
|
|
1199
|
+
off(event, handler) {
|
|
1200
|
+
this.sdk.off(event, handler);
|
|
1201
|
+
}
|
|
1202
|
+
// --- Capability discovery ---
|
|
1203
|
+
async listAvailableAgents() {
|
|
1204
|
+
this.ensureNotDisposed();
|
|
1205
|
+
const sdk = this.sdk;
|
|
1206
|
+
if (typeof sdk.listAvailableAgents === "function") {
|
|
1207
|
+
return sdk.listAvailableAgents();
|
|
1208
|
+
}
|
|
1209
|
+
return [];
|
|
1210
|
+
}
|
|
1211
|
+
// --- Internal ---
|
|
1212
|
+
ensureNotDisposed() {
|
|
1213
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
1214
|
+
}
|
|
1215
|
+
registerConnectionEvents() {
|
|
1216
|
+
this.sdk.on("connected", this.handleSdkConnected);
|
|
1217
|
+
this.sdk.on("disconnected", this.handleSdkDisconnected);
|
|
1218
|
+
this.sdk.on("error", this.handleSdkError);
|
|
1219
|
+
}
|
|
1220
|
+
unregisterConnectionEvents() {
|
|
1221
|
+
this.sdk.off("connected", this.handleSdkConnected);
|
|
1222
|
+
this.sdk.off("disconnected", this.handleSdkDisconnected);
|
|
1223
|
+
this.sdk.off("error", this.handleSdkError);
|
|
1224
|
+
}
|
|
1225
|
+
updateConnection(status, options = {}) {
|
|
1226
|
+
const nextError = status === "error" ? options.error : void 0;
|
|
1227
|
+
const nextErrorCode = status === "error" ? options.errorCode : void 0;
|
|
1228
|
+
if (this.connectionStatus === status && this.connectionError === nextError && this.connectionErrorCode === nextErrorCode) {
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
this.connectionStatus = status;
|
|
1232
|
+
this.connectionError = nextError;
|
|
1233
|
+
this.connectionErrorCode = nextErrorCode;
|
|
1234
|
+
this.connectionLastChangedAt = Date.now();
|
|
1235
|
+
const snapshot = this.getConnectionSnapshot();
|
|
1236
|
+
for (const listener of this.connectionListeners) {
|
|
1237
|
+
listener(snapshot);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
function normalizeConnectionError(error) {
|
|
1242
|
+
if (error instanceof Error) {
|
|
1243
|
+
const code = error.code;
|
|
1244
|
+
return {
|
|
1245
|
+
error: error.message || "Connection failed",
|
|
1246
|
+
errorCode: typeof code === "string" && code.trim() ? code : "CONNECTION_FAILED"
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
if (error && typeof error === "object") {
|
|
1250
|
+
const record = error;
|
|
1251
|
+
const message = typeof record.message === "string" ? record.message : "Connection failed";
|
|
1252
|
+
const code = typeof record.code === "string" && record.code.trim() ? record.code : "CONNECTION_FAILED";
|
|
1253
|
+
return { error: message, errorCode: code };
|
|
1254
|
+
}
|
|
1255
|
+
if (typeof error === "string" && error.trim()) {
|
|
1256
|
+
return { error, errorCode: "CONNECTION_FAILED" };
|
|
1257
|
+
}
|
|
1258
|
+
return { error: "Connection failed", errorCode: "CONNECTION_FAILED" };
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/providers/sanqian/mock-sdk.ts
|
|
1262
|
+
var MockSanqianSdk = class {
|
|
1263
|
+
appName;
|
|
1264
|
+
connected;
|
|
1265
|
+
readinessError;
|
|
1266
|
+
scenarios;
|
|
1267
|
+
agents = /* @__PURE__ */ new Map();
|
|
1268
|
+
conversations = /* @__PURE__ */ new Map();
|
|
1269
|
+
sessionResources = [];
|
|
1270
|
+
listeners = /* @__PURE__ */ new Map();
|
|
1271
|
+
embeddingConfig;
|
|
1272
|
+
rerankConfig;
|
|
1273
|
+
// Tracking for assertions
|
|
1274
|
+
connectCalls = [];
|
|
1275
|
+
disconnectCalls = [];
|
|
1276
|
+
ensureReadyCalls = [];
|
|
1277
|
+
createAgentCalls = [];
|
|
1278
|
+
chatCalls = [];
|
|
1279
|
+
chatStreamCalls = [];
|
|
1280
|
+
cancelRunCalls = [];
|
|
1281
|
+
hitlResponseCalls = [];
|
|
1282
|
+
constructor(config = {}) {
|
|
1283
|
+
this.appName = config.appName ?? "test-app";
|
|
1284
|
+
this.connected = config.connected ?? false;
|
|
1285
|
+
this.readinessError = config.readinessError ?? null;
|
|
1286
|
+
this.scenarios = config.scenarios ?? [];
|
|
1287
|
+
this.embeddingConfig = config.embeddingConfig ?? { available: false };
|
|
1288
|
+
this.rerankConfig = config.rerankConfig ?? { available: false };
|
|
1289
|
+
}
|
|
1290
|
+
// --- Connection ---
|
|
1291
|
+
async connect() {
|
|
1292
|
+
this.connectCalls.push(Date.now());
|
|
1293
|
+
if (this.readinessError) throw new Error(this.readinessError);
|
|
1294
|
+
this.connected = true;
|
|
1295
|
+
this.emit("connected");
|
|
1296
|
+
}
|
|
1297
|
+
async disconnect() {
|
|
1298
|
+
this.disconnectCalls.push(Date.now());
|
|
1299
|
+
this.connected = false;
|
|
1300
|
+
this.emit("disconnected");
|
|
1301
|
+
}
|
|
1302
|
+
async ensureReady() {
|
|
1303
|
+
this.ensureReadyCalls.push(Date.now());
|
|
1304
|
+
if (this.readinessError) throw new Error(this.readinessError);
|
|
1305
|
+
if (!this.connected) {
|
|
1306
|
+
await this.connect();
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
isConnected() {
|
|
1310
|
+
return this.connected;
|
|
1311
|
+
}
|
|
1312
|
+
acquireReconnect() {
|
|
1313
|
+
}
|
|
1314
|
+
releaseReconnect() {
|
|
1315
|
+
}
|
|
1316
|
+
// --- Events ---
|
|
1317
|
+
on(event, handler) {
|
|
1318
|
+
if (!this.listeners.has(event)) {
|
|
1319
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
1320
|
+
}
|
|
1321
|
+
this.listeners.get(event).add(handler);
|
|
1322
|
+
}
|
|
1323
|
+
off(event, handler) {
|
|
1324
|
+
this.listeners.get(event)?.delete(handler);
|
|
1325
|
+
}
|
|
1326
|
+
removeAllListeners() {
|
|
1327
|
+
this.listeners.clear();
|
|
1328
|
+
}
|
|
1329
|
+
emit(event, ...args) {
|
|
1330
|
+
const handlers = this.listeners.get(event);
|
|
1331
|
+
if (handlers) {
|
|
1332
|
+
for (const handler of handlers) {
|
|
1333
|
+
handler(...args);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
// --- Agents ---
|
|
1338
|
+
async createAgent(config) {
|
|
1339
|
+
this.createAgentCalls.push(config);
|
|
1340
|
+
const registeredId = `${this.appName}:${config.agent_id}`;
|
|
1341
|
+
this.agents.set(config.agent_id, { agent_id: registeredId });
|
|
1342
|
+
return { agent_id: registeredId };
|
|
1343
|
+
}
|
|
1344
|
+
// --- Chat ---
|
|
1345
|
+
async chat(agentId, messages, options) {
|
|
1346
|
+
this.chatCalls.push({ agentId, messages, options });
|
|
1347
|
+
const scenario = this.findScenario(agentId);
|
|
1348
|
+
if (scenario?.error) throw new Error(scenario.error);
|
|
1349
|
+
const content = scenario?.chatResponse ?? "Mock response";
|
|
1350
|
+
return {
|
|
1351
|
+
message: { content, role: "assistant" },
|
|
1352
|
+
conversationId: options?.conversationId ?? "conv-mock",
|
|
1353
|
+
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
async *chatStream(agentId, messages, options) {
|
|
1357
|
+
this.chatStreamCalls.push({ agentId, messages, options });
|
|
1358
|
+
const scenario = this.findScenario(agentId);
|
|
1359
|
+
if (scenario?.error) {
|
|
1360
|
+
yield { type: "error", error: scenario.error };
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
if (scenario?.streamEvents) {
|
|
1364
|
+
for (const event of scenario.streamEvents) {
|
|
1365
|
+
if (options?.signal?.aborted) {
|
|
1366
|
+
yield { type: "cancelled", run_id: "run-mock" };
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
yield event;
|
|
1370
|
+
}
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
if (options?.signal?.aborted) {
|
|
1374
|
+
yield { type: "cancelled", run_id: "run-mock" };
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
yield { type: "start", run_id: "run-mock" };
|
|
1378
|
+
if (options?.signal?.aborted) {
|
|
1379
|
+
yield { type: "cancelled", run_id: "run-mock" };
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
yield { type: "text", content: scenario?.chatResponse ?? "Mock streamed response" };
|
|
1383
|
+
yield { type: "done", conversationId: options?.conversationId ?? "conv-mock" };
|
|
1384
|
+
}
|
|
1385
|
+
// --- Cancel / HITL ---
|
|
1386
|
+
cancelRun(runId) {
|
|
1387
|
+
this.cancelRunCalls.push(runId);
|
|
1388
|
+
}
|
|
1389
|
+
sendHitlResponse(runId, response) {
|
|
1390
|
+
this.hitlResponseCalls.push({ runId, response });
|
|
1391
|
+
}
|
|
1392
|
+
// --- Conversations ---
|
|
1393
|
+
async listConversations(options) {
|
|
1394
|
+
const all = Array.from(this.conversations.values());
|
|
1395
|
+
const filtered = options.agentId ? all.filter((c) => c.agent_id === options.agentId) : all;
|
|
1396
|
+
const offset = options.offset ?? 0;
|
|
1397
|
+
const limit = options.limit ?? 20;
|
|
1398
|
+
return {
|
|
1399
|
+
conversations: filtered.slice(offset, offset + limit),
|
|
1400
|
+
total: filtered.length
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
async getConversation(id) {
|
|
1404
|
+
const conv = this.conversations.get(id);
|
|
1405
|
+
if (!conv) throw new Error(`Conversation not found: ${id}`);
|
|
1406
|
+
return conv;
|
|
1407
|
+
}
|
|
1408
|
+
async deleteConversation(id) {
|
|
1409
|
+
this.conversations.delete(id);
|
|
1410
|
+
}
|
|
1411
|
+
/** Test helper: seed a conversation */
|
|
1412
|
+
seedConversation(detail) {
|
|
1413
|
+
this.conversations.set(detail.conversation_id, detail);
|
|
1414
|
+
}
|
|
1415
|
+
// --- Session Resources ---
|
|
1416
|
+
getSessionResources() {
|
|
1417
|
+
return [...this.sessionResources];
|
|
1418
|
+
}
|
|
1419
|
+
async pushResource(resource) {
|
|
1420
|
+
const stored = {
|
|
1421
|
+
...resource,
|
|
1422
|
+
fullId: `${this.appName}:${resource.id ?? `res-${Date.now()}`}`,
|
|
1423
|
+
appName: this.appName,
|
|
1424
|
+
pushedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1425
|
+
};
|
|
1426
|
+
this.sessionResources.push(stored);
|
|
1427
|
+
this.emit("resourcePushed", stored);
|
|
1428
|
+
return stored;
|
|
1429
|
+
}
|
|
1430
|
+
async removeResource(resourceId) {
|
|
1431
|
+
this.sessionResources = this.sessionResources.filter((r) => r.fullId !== resourceId);
|
|
1432
|
+
this.emit("resourceRemoved", resourceId);
|
|
1433
|
+
}
|
|
1434
|
+
async fetchSessionResources() {
|
|
1435
|
+
return [...this.sessionResources];
|
|
1436
|
+
}
|
|
1437
|
+
// --- Embedding / Rerank ---
|
|
1438
|
+
async getEmbeddingConfig() {
|
|
1439
|
+
return this.embeddingConfig;
|
|
1440
|
+
}
|
|
1441
|
+
async getRerankConfig() {
|
|
1442
|
+
return this.rerankConfig;
|
|
1443
|
+
}
|
|
1444
|
+
// --- Helpers ---
|
|
1445
|
+
findScenario(agentId) {
|
|
1446
|
+
return this.scenarios.find((s) => !s.agentId || s.agentId === agentId);
|
|
1447
|
+
}
|
|
1448
|
+
/** Simulate readiness failure for testing rollback */
|
|
1449
|
+
setReadinessError(error) {
|
|
1450
|
+
this.readinessError = error;
|
|
1451
|
+
}
|
|
1452
|
+
/** Force connection state for testing */
|
|
1453
|
+
setConnected(connected) {
|
|
1454
|
+
this.connected = connected;
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
// src/providers/sanqian/embedding-provider.ts
|
|
1459
|
+
var DEFAULT_BATCH_SIZE = 50;
|
|
1460
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
1461
|
+
var SanqianEmbeddingProvider = class {
|
|
1462
|
+
id;
|
|
1463
|
+
model;
|
|
1464
|
+
dimensions;
|
|
1465
|
+
config;
|
|
1466
|
+
disposed = false;
|
|
1467
|
+
constructor(config, id) {
|
|
1468
|
+
this.config = config;
|
|
1469
|
+
this.id = id ?? "sanqian-embedding";
|
|
1470
|
+
this.model = config.modelName;
|
|
1471
|
+
this.dimensions = config.dimensions;
|
|
1472
|
+
}
|
|
1473
|
+
async ensureReady() {
|
|
1474
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
1475
|
+
if (!this.config.apiUrl) throw new Error("Embedding API URL not configured");
|
|
1476
|
+
if (this.config.apiType !== "local" && !this.config.apiKey) {
|
|
1477
|
+
throw new Error("Embedding API Key not configured");
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
async dispose() {
|
|
1481
|
+
this.disposed = true;
|
|
1482
|
+
}
|
|
1483
|
+
async embedMany(texts, options) {
|
|
1484
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
1485
|
+
if (texts.length === 0) return [];
|
|
1486
|
+
if (texts.length <= DEFAULT_BATCH_SIZE) {
|
|
1487
|
+
return this.callApi(texts, options?.signal);
|
|
1488
|
+
}
|
|
1489
|
+
const allEmbeddings = [];
|
|
1490
|
+
for (let i = 0; i < texts.length; i += DEFAULT_BATCH_SIZE) {
|
|
1491
|
+
if (options?.signal?.aborted) throw new Error("Embedding request aborted");
|
|
1492
|
+
const batch = texts.slice(i, i + DEFAULT_BATCH_SIZE);
|
|
1493
|
+
const batchEmbeddings = await this.callApi(batch, options?.signal);
|
|
1494
|
+
allEmbeddings.push(...batchEmbeddings);
|
|
1495
|
+
}
|
|
1496
|
+
return allEmbeddings;
|
|
1497
|
+
}
|
|
1498
|
+
async embed(text, options) {
|
|
1499
|
+
const [result] = await this.embedMany([text], options);
|
|
1500
|
+
if (!result) throw new Error("Failed to generate embedding");
|
|
1501
|
+
return result;
|
|
1502
|
+
}
|
|
1503
|
+
async test() {
|
|
1504
|
+
try {
|
|
1505
|
+
const embedding = await this.embed("Hello, this is a test.");
|
|
1506
|
+
return { success: true, dimensions: embedding.length };
|
|
1507
|
+
} catch (error) {
|
|
1508
|
+
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
async callApi(texts, signal) {
|
|
1512
|
+
const { apiUrl, apiKey, modelName, apiType } = this.config;
|
|
1513
|
+
if (apiType === "local") {
|
|
1514
|
+
return this.callOllama(texts, signal);
|
|
1515
|
+
}
|
|
1516
|
+
const headers = {
|
|
1517
|
+
"Content-Type": "application/json",
|
|
1518
|
+
"Authorization": `Bearer ${apiKey}`
|
|
1519
|
+
};
|
|
1520
|
+
const response = await fetch(apiUrl, {
|
|
1521
|
+
method: "POST",
|
|
1522
|
+
headers,
|
|
1523
|
+
body: JSON.stringify({ input: texts, model: modelName }),
|
|
1524
|
+
signal: signal ?? AbortSignal.timeout(DEFAULT_TIMEOUT)
|
|
1525
|
+
});
|
|
1526
|
+
if (!response.ok) {
|
|
1527
|
+
const errorText = await response.text();
|
|
1528
|
+
throw new Error(`Embedding API error: ${response.status} - ${errorText.slice(0, 100)}`);
|
|
1529
|
+
}
|
|
1530
|
+
const data = await response.json();
|
|
1531
|
+
const sorted = data.data.sort((a, b) => a.index - b.index);
|
|
1532
|
+
const embeddings = sorted.map((item) => item.embedding);
|
|
1533
|
+
if (embeddings.length !== texts.length) {
|
|
1534
|
+
throw new Error(`Embedding count mismatch: expected ${texts.length}, got ${embeddings.length}`);
|
|
1535
|
+
}
|
|
1536
|
+
return embeddings;
|
|
1537
|
+
}
|
|
1538
|
+
async callOllama(texts, signal) {
|
|
1539
|
+
const { apiUrl, apiKey, modelName } = this.config;
|
|
1540
|
+
const headers = { "Content-Type": "application/json" };
|
|
1541
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
1542
|
+
const embeddings = [];
|
|
1543
|
+
for (const text of texts) {
|
|
1544
|
+
if (signal?.aborted) throw new Error("Embedding request aborted");
|
|
1545
|
+
const response = await fetch(apiUrl, {
|
|
1546
|
+
method: "POST",
|
|
1547
|
+
headers,
|
|
1548
|
+
body: JSON.stringify({ model: modelName, prompt: text }),
|
|
1549
|
+
signal: signal ?? AbortSignal.timeout(DEFAULT_TIMEOUT)
|
|
1550
|
+
});
|
|
1551
|
+
if (!response.ok) throw new Error(`Ollama API error: ${response.status}`);
|
|
1552
|
+
const data = await response.json();
|
|
1553
|
+
embeddings.push(data.embedding);
|
|
1554
|
+
}
|
|
1555
|
+
return embeddings;
|
|
1556
|
+
}
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
// src/providers/sanqian/rerank-provider.ts
|
|
1560
|
+
var SanqianRerankProvider = class {
|
|
1561
|
+
id;
|
|
1562
|
+
model;
|
|
1563
|
+
config;
|
|
1564
|
+
disposed = false;
|
|
1565
|
+
constructor(config, id) {
|
|
1566
|
+
this.id = id ?? "sanqian-rerank";
|
|
1567
|
+
this.model = config.modelName;
|
|
1568
|
+
this.config = config;
|
|
1569
|
+
}
|
|
1570
|
+
async ensureReady() {
|
|
1571
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
1572
|
+
if (!this.config.apiUrl) throw new Error("Rerank API URL not configured");
|
|
1573
|
+
if (!this.config.apiKey) throw new Error("Rerank API Key not configured");
|
|
1574
|
+
}
|
|
1575
|
+
async dispose() {
|
|
1576
|
+
this.disposed = true;
|
|
1577
|
+
}
|
|
1578
|
+
async rerank(input, options) {
|
|
1579
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
1580
|
+
if (input.documents.length === 0) return { results: [] };
|
|
1581
|
+
const { apiUrl, apiKey, modelName } = this.config;
|
|
1582
|
+
const response = await fetch(apiUrl, {
|
|
1583
|
+
method: "POST",
|
|
1584
|
+
headers: {
|
|
1585
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
1586
|
+
"Content-Type": "application/json"
|
|
1587
|
+
},
|
|
1588
|
+
body: JSON.stringify({
|
|
1589
|
+
model: modelName,
|
|
1590
|
+
query: input.query,
|
|
1591
|
+
documents: input.documents.map((d) => d.text),
|
|
1592
|
+
top_n: input.topN ?? input.documents.length,
|
|
1593
|
+
return_documents: false
|
|
1594
|
+
}),
|
|
1595
|
+
signal: options?.signal
|
|
1596
|
+
});
|
|
1597
|
+
if (!response.ok) {
|
|
1598
|
+
const errorText = await response.text();
|
|
1599
|
+
throw new Error(`Rerank API error: ${response.status} - ${errorText.slice(0, 100)}`);
|
|
1600
|
+
}
|
|
1601
|
+
const data = await response.json();
|
|
1602
|
+
if (!data.results?.length) return { results: [] };
|
|
1603
|
+
const results = data.results.filter((r) => r.index >= 0 && r.index < input.documents.length).sort((a, b) => b.relevance_score - a.relevance_score).map((r) => ({
|
|
1604
|
+
id: input.documents[r.index].id,
|
|
1605
|
+
score: r.relevance_score,
|
|
1606
|
+
index: r.index
|
|
1607
|
+
}));
|
|
1608
|
+
return { results };
|
|
1609
|
+
}
|
|
1610
|
+
async test() {
|
|
1611
|
+
try {
|
|
1612
|
+
const result = await this.rerank({
|
|
1613
|
+
query: "test",
|
|
1614
|
+
documents: [{ id: "1", text: "doc one" }, { id: "2", text: "doc two" }],
|
|
1615
|
+
topN: 2
|
|
1616
|
+
});
|
|
1617
|
+
return { success: result.results.length > 0 };
|
|
1618
|
+
} catch (error) {
|
|
1619
|
+
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
// src/providers/vercel/provider.ts
|
|
1625
|
+
var import_ai = require("ai");
|
|
1626
|
+
var import_openai_compatible = require("@ai-sdk/openai-compatible");
|
|
1627
|
+
|
|
1628
|
+
// src/providers/vercel/tool-converter.ts
|
|
1629
|
+
var import_zod = require("zod");
|
|
1630
|
+
function jsonSchemaToZod(schema) {
|
|
1631
|
+
switch (schema.type) {
|
|
1632
|
+
case "string":
|
|
1633
|
+
return import_zod.z.string();
|
|
1634
|
+
case "number":
|
|
1635
|
+
return import_zod.z.number();
|
|
1636
|
+
case "integer":
|
|
1637
|
+
return import_zod.z.number().int();
|
|
1638
|
+
case "boolean":
|
|
1639
|
+
return import_zod.z.boolean();
|
|
1640
|
+
case "array": {
|
|
1641
|
+
const items = schema.items;
|
|
1642
|
+
return import_zod.z.array(items ? jsonSchemaToZod(items) : import_zod.z.unknown());
|
|
1643
|
+
}
|
|
1644
|
+
case "object":
|
|
1645
|
+
break;
|
|
1646
|
+
}
|
|
1647
|
+
const properties = schema.properties;
|
|
1648
|
+
const required = schema.required ?? [];
|
|
1649
|
+
if (!properties || Object.keys(properties).length === 0) {
|
|
1650
|
+
return import_zod.z.object({});
|
|
1651
|
+
}
|
|
1652
|
+
const shape = {};
|
|
1653
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
1654
|
+
let field;
|
|
1655
|
+
switch (prop.type) {
|
|
1656
|
+
case "string":
|
|
1657
|
+
field = import_zod.z.string();
|
|
1658
|
+
break;
|
|
1659
|
+
case "number":
|
|
1660
|
+
case "integer":
|
|
1661
|
+
field = import_zod.z.number();
|
|
1662
|
+
break;
|
|
1663
|
+
case "boolean":
|
|
1664
|
+
field = import_zod.z.boolean();
|
|
1665
|
+
break;
|
|
1666
|
+
case "array": {
|
|
1667
|
+
const items = prop.items;
|
|
1668
|
+
field = import_zod.z.array(items ? jsonSchemaToZod(items) : import_zod.z.unknown());
|
|
1669
|
+
break;
|
|
1670
|
+
}
|
|
1671
|
+
case "object":
|
|
1672
|
+
field = jsonSchemaToZod(prop);
|
|
1673
|
+
break;
|
|
1674
|
+
default:
|
|
1675
|
+
field = import_zod.z.unknown();
|
|
1676
|
+
}
|
|
1677
|
+
if (prop.description) {
|
|
1678
|
+
field = field.describe(prop.description);
|
|
1679
|
+
}
|
|
1680
|
+
if (!required.includes(key)) {
|
|
1681
|
+
field = field.optional();
|
|
1682
|
+
}
|
|
1683
|
+
shape[key] = field;
|
|
1684
|
+
}
|
|
1685
|
+
return import_zod.z.object(shape);
|
|
1686
|
+
}
|
|
1687
|
+
function convertToolsForVercel(tools, registry, options) {
|
|
1688
|
+
const result = {};
|
|
1689
|
+
for (const tool of tools) {
|
|
1690
|
+
const zodSchema = jsonSchemaToZod(tool.parameters);
|
|
1691
|
+
if (registry) {
|
|
1692
|
+
const reg = registry;
|
|
1693
|
+
result[tool.name] = {
|
|
1694
|
+
type: "function",
|
|
1695
|
+
description: tool.description,
|
|
1696
|
+
inputSchema: zodSchema,
|
|
1697
|
+
execute: async (args) => {
|
|
1698
|
+
const execResult = await reg.execute({
|
|
1699
|
+
toolCallId: `tc-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
1700
|
+
name: tool.name,
|
|
1701
|
+
args,
|
|
1702
|
+
signal: options?.signal
|
|
1703
|
+
});
|
|
1704
|
+
if (!execResult.success) {
|
|
1705
|
+
throw new Error(execResult.error ?? "Tool execution failed");
|
|
1706
|
+
}
|
|
1707
|
+
return execResult.result;
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
} else {
|
|
1711
|
+
result[tool.name] = {
|
|
1712
|
+
type: "function",
|
|
1713
|
+
description: tool.description,
|
|
1714
|
+
inputSchema: zodSchema
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return result;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// src/providers/vercel/provider.ts
|
|
1722
|
+
var VERCEL_CAPABILITIES = {
|
|
1723
|
+
streamingText: true,
|
|
1724
|
+
reasoning: true,
|
|
1725
|
+
toolCalling: true,
|
|
1726
|
+
parallelToolCalls: false,
|
|
1727
|
+
// serialize mutations for safety
|
|
1728
|
+
toolArgsStreaming: false,
|
|
1729
|
+
abort: true,
|
|
1730
|
+
hitl: false,
|
|
1731
|
+
// local permission policy, not backend HITL
|
|
1732
|
+
conversations: false,
|
|
1733
|
+
// needs local ConversationStore
|
|
1734
|
+
sessionResources: false,
|
|
1735
|
+
resourcePicker: false,
|
|
1736
|
+
embeddings: false,
|
|
1737
|
+
rerank: false
|
|
1738
|
+
};
|
|
1739
|
+
var DEFAULT_MAX_STEPS = 5;
|
|
1740
|
+
var TOOL_STEP_LIMIT_ERROR = "Tool step limit reached before the model produced a final response.";
|
|
1741
|
+
var TOOL_STEP_LIMIT_ERROR_CODE = "TOOL_STEP_LIMIT";
|
|
1742
|
+
var VercelProvider = class {
|
|
1743
|
+
id;
|
|
1744
|
+
kind = "vercel-ai-sdk";
|
|
1745
|
+
displayName;
|
|
1746
|
+
capabilities = VERCEL_CAPABILITIES;
|
|
1747
|
+
apiKey;
|
|
1748
|
+
baseURL;
|
|
1749
|
+
modelName;
|
|
1750
|
+
providerName;
|
|
1751
|
+
agentConfigs;
|
|
1752
|
+
toolRegistry;
|
|
1753
|
+
maxSteps;
|
|
1754
|
+
disposed = false;
|
|
1755
|
+
ready = false;
|
|
1756
|
+
constructor(config) {
|
|
1757
|
+
this.id = config.id ?? "vercel";
|
|
1758
|
+
this.displayName = config.displayName ?? config.providerName ?? "Vercel AI";
|
|
1759
|
+
this.apiKey = config.apiKey;
|
|
1760
|
+
this.baseURL = config.baseURL;
|
|
1761
|
+
this.modelName = config.model;
|
|
1762
|
+
this.providerName = config.providerName ?? "openai-compatible";
|
|
1763
|
+
this.toolRegistry = config.toolRegistry ?? null;
|
|
1764
|
+
this.maxSteps = config.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
1765
|
+
this.agentConfigs = /* @__PURE__ */ new Map();
|
|
1766
|
+
for (const agent of config.agents ?? []) {
|
|
1767
|
+
this.agentConfigs.set(agent.id, agent);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
// --- Lifecycle ---
|
|
1771
|
+
async ensureReady() {
|
|
1772
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
1773
|
+
if (this.ready) return;
|
|
1774
|
+
if (!this.apiKey) throw new Error("API key is required");
|
|
1775
|
+
if (!this.baseURL) throw new Error("Base URL is required");
|
|
1776
|
+
if (!this.modelName) throw new Error("Model name is required");
|
|
1777
|
+
const provider = this.createProvider();
|
|
1778
|
+
try {
|
|
1779
|
+
await (0, import_ai.generateText)({
|
|
1780
|
+
model: provider.chatModel(this.modelName),
|
|
1781
|
+
prompt: "Reply with OK",
|
|
1782
|
+
maxOutputTokens: 32,
|
|
1783
|
+
abortSignal: AbortSignal.timeout(15e3)
|
|
1784
|
+
});
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
const message = error instanceof Error ? error.message : "Provider readiness check failed";
|
|
1787
|
+
const redacted = message.replace(new RegExp(this.apiKey.slice(0, 8), "g"), "****");
|
|
1788
|
+
throw new Error(`Provider readiness failed: ${redacted}`);
|
|
1789
|
+
}
|
|
1790
|
+
this.ready = true;
|
|
1791
|
+
}
|
|
1792
|
+
async dispose() {
|
|
1793
|
+
this.disposed = true;
|
|
1794
|
+
this.ready = false;
|
|
1795
|
+
}
|
|
1796
|
+
isDisposed() {
|
|
1797
|
+
return this.disposed;
|
|
1798
|
+
}
|
|
1799
|
+
// --- Chat ---
|
|
1800
|
+
async chat(input) {
|
|
1801
|
+
this.ensureNotDisposed();
|
|
1802
|
+
const agentConfig = this.agentConfigs.get(input.agentId);
|
|
1803
|
+
const provider = this.createProvider();
|
|
1804
|
+
const model = provider.chatModel(this.modelName);
|
|
1805
|
+
const { system, userMessages } = this.buildMessages(input.messages, agentConfig);
|
|
1806
|
+
const tools = this.resolveTools(input, agentConfig);
|
|
1807
|
+
const maxSteps = tools ? this.maxSteps : 1;
|
|
1808
|
+
const result = await (0, import_ai.generateText)({
|
|
1809
|
+
model,
|
|
1810
|
+
system,
|
|
1811
|
+
messages: userMessages,
|
|
1812
|
+
tools,
|
|
1813
|
+
stopWhen: (0, import_ai.stepCountIs)(maxSteps),
|
|
1814
|
+
abortSignal: input.signal
|
|
1815
|
+
});
|
|
1816
|
+
const resultWithSteps = result;
|
|
1817
|
+
if (tools && resultWithSteps.finishReason === "tool-calls" && (resultWithSteps.steps?.length ?? 0) >= maxSteps) {
|
|
1818
|
+
throw new Error(TOOL_STEP_LIMIT_ERROR);
|
|
1819
|
+
}
|
|
1820
|
+
const usage = result.usage;
|
|
1821
|
+
return {
|
|
1822
|
+
content: result.text,
|
|
1823
|
+
usage: usage ? {
|
|
1824
|
+
promptTokens: usage.inputTokens ?? 0,
|
|
1825
|
+
completionTokens: usage.outputTokens ?? 0,
|
|
1826
|
+
totalTokens: (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0)
|
|
1827
|
+
} : void 0
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
async *chatStream(input) {
|
|
1831
|
+
this.ensureNotDisposed();
|
|
1832
|
+
const agentConfig = this.agentConfigs.get(input.agentId);
|
|
1833
|
+
const provider = this.createProvider();
|
|
1834
|
+
const model = provider.chatModel(this.modelName);
|
|
1835
|
+
const { system, userMessages } = this.buildMessages(input.messages, agentConfig);
|
|
1836
|
+
const tools = this.resolveTools(input, agentConfig);
|
|
1837
|
+
const maxSteps = tools ? this.maxSteps : 1;
|
|
1838
|
+
const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
1839
|
+
yield { type: "start", runId };
|
|
1840
|
+
try {
|
|
1841
|
+
let completedSteps = 0;
|
|
1842
|
+
let finishReason;
|
|
1843
|
+
const result = (0, import_ai.streamText)({
|
|
1844
|
+
model,
|
|
1845
|
+
system,
|
|
1846
|
+
messages: userMessages,
|
|
1847
|
+
tools,
|
|
1848
|
+
stopWhen: (0, import_ai.stepCountIs)(maxSteps),
|
|
1849
|
+
abortSignal: input.signal
|
|
1850
|
+
});
|
|
1851
|
+
for await (const part of result.fullStream) {
|
|
1852
|
+
if (input.signal?.aborted) {
|
|
1853
|
+
yield { type: "cancelled", runId };
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
const partType = part.type;
|
|
1857
|
+
switch (partType) {
|
|
1858
|
+
case "text-delta": {
|
|
1859
|
+
const textPart = part;
|
|
1860
|
+
if (textPart.text) {
|
|
1861
|
+
yield { type: "text", content: textPart.text };
|
|
1862
|
+
}
|
|
1863
|
+
break;
|
|
1864
|
+
}
|
|
1865
|
+
case "reasoning-delta": {
|
|
1866
|
+
const reasoningPart = part;
|
|
1867
|
+
if (reasoningPart.text) {
|
|
1868
|
+
yield { type: "thinking", content: reasoningPart.text };
|
|
1869
|
+
}
|
|
1870
|
+
break;
|
|
1871
|
+
}
|
|
1872
|
+
case "tool-call": {
|
|
1873
|
+
const toolPart = part;
|
|
1874
|
+
yield {
|
|
1875
|
+
type: "tool_call",
|
|
1876
|
+
toolCall: {
|
|
1877
|
+
id: toolPart.toolCallId,
|
|
1878
|
+
name: toolPart.toolName,
|
|
1879
|
+
arguments: JSON.stringify(toolPart.input)
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
break;
|
|
1883
|
+
}
|
|
1884
|
+
case "tool-result": {
|
|
1885
|
+
const resultPart = part;
|
|
1886
|
+
yield {
|
|
1887
|
+
type: "tool_result",
|
|
1888
|
+
toolCallId: resultPart.toolCallId,
|
|
1889
|
+
result: resultPart.output,
|
|
1890
|
+
success: true
|
|
1891
|
+
};
|
|
1892
|
+
break;
|
|
1893
|
+
}
|
|
1894
|
+
case "finish-step": {
|
|
1895
|
+
const finishPart = part;
|
|
1896
|
+
completedSteps += 1;
|
|
1897
|
+
finishReason = finishPart.finishReason;
|
|
1898
|
+
break;
|
|
1899
|
+
}
|
|
1900
|
+
case "finish": {
|
|
1901
|
+
const finishPart = part;
|
|
1902
|
+
finishReason = finishPart.finishReason;
|
|
1903
|
+
break;
|
|
1904
|
+
}
|
|
1905
|
+
case "tool-error": {
|
|
1906
|
+
const errorPart = part;
|
|
1907
|
+
const errMsg = errorPart.error instanceof Error ? errorPart.error.message : String(errorPart.error);
|
|
1908
|
+
yield {
|
|
1909
|
+
type: "tool_result",
|
|
1910
|
+
toolCallId: errorPart.id ?? errorPart.toolCallId ?? "",
|
|
1911
|
+
success: false,
|
|
1912
|
+
error: this.redactSecrets(errMsg)
|
|
1913
|
+
};
|
|
1914
|
+
break;
|
|
1915
|
+
}
|
|
1916
|
+
case "error": {
|
|
1917
|
+
const errPart = part;
|
|
1918
|
+
const errorMessage = errPart.error instanceof Error ? errPart.error.message : String(errPart.error ?? "Unknown error");
|
|
1919
|
+
yield { type: "error", error: this.redactSecrets(errorMessage) };
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
// text-start, text-end, reasoning-start, reasoning-end,
|
|
1923
|
+
// tool-input-start, tool-input-end, tool-input-delta,
|
|
1924
|
+
// start-step, source, file - skip
|
|
1925
|
+
default:
|
|
1926
|
+
break;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
if (tools && finishReason === "tool-calls" && completedSteps >= maxSteps) {
|
|
1930
|
+
yield {
|
|
1931
|
+
type: "error",
|
|
1932
|
+
error: TOOL_STEP_LIMIT_ERROR,
|
|
1933
|
+
code: TOOL_STEP_LIMIT_ERROR_CODE
|
|
1934
|
+
};
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
yield { type: "done", conversationId: input.conversationId ?? "" };
|
|
1938
|
+
} catch (error) {
|
|
1939
|
+
if (input.signal?.aborted) {
|
|
1940
|
+
yield { type: "cancelled", runId };
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
const message = error instanceof Error ? error.message : "Stream error";
|
|
1944
|
+
yield { type: "error", error: this.redactSecrets(message) };
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
// --- Cancel ---
|
|
1948
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1949
|
+
async cancel(_runId) {
|
|
1950
|
+
}
|
|
1951
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1952
|
+
async sendPermissionResponse(_input) {
|
|
1953
|
+
}
|
|
1954
|
+
// --- Internal ---
|
|
1955
|
+
createProvider() {
|
|
1956
|
+
return (0, import_openai_compatible.createOpenAICompatible)({
|
|
1957
|
+
name: this.providerName,
|
|
1958
|
+
apiKey: this.apiKey,
|
|
1959
|
+
baseURL: this.baseURL
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
buildMessages(messages, agentConfig) {
|
|
1963
|
+
const systemParts = [];
|
|
1964
|
+
if (agentConfig?.systemPrompt) {
|
|
1965
|
+
systemParts.push(agentConfig.systemPrompt);
|
|
1966
|
+
}
|
|
1967
|
+
const userMessages = [];
|
|
1968
|
+
for (const msg of messages) {
|
|
1969
|
+
if (msg.role === "system") {
|
|
1970
|
+
if (!agentConfig?.systemPrompt || msg.content !== agentConfig.systemPrompt) {
|
|
1971
|
+
systemParts.push(msg.content);
|
|
1972
|
+
}
|
|
1973
|
+
} else {
|
|
1974
|
+
userMessages.push({
|
|
1975
|
+
role: msg.role,
|
|
1976
|
+
content: msg.content
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
return {
|
|
1981
|
+
system: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
|
|
1982
|
+
userMessages
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
resolveTools(input, agentConfig) {
|
|
1986
|
+
const toolDefs = input.tools;
|
|
1987
|
+
if (toolDefs && toolDefs.length > 0) {
|
|
1988
|
+
return convertToolsForVercel(toolDefs, this.toolRegistry, { signal: input.signal });
|
|
1989
|
+
}
|
|
1990
|
+
if (agentConfig?.tools && agentConfig.tools.length > 0 && this.toolRegistry) {
|
|
1991
|
+
const registeredTools = agentConfig.tools.map((name) => this.toolRegistry.get(name)).filter((t) => t !== void 0).map((t) => t.definition);
|
|
1992
|
+
if (registeredTools.length > 0) {
|
|
1993
|
+
return convertToolsForVercel(registeredTools, this.toolRegistry, { signal: input.signal });
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
return void 0;
|
|
1997
|
+
}
|
|
1998
|
+
redactSecrets(message) {
|
|
1999
|
+
if (this.apiKey && this.apiKey.length > 8) {
|
|
2000
|
+
const prefix = this.apiKey.slice(0, 8);
|
|
2001
|
+
return message.replace(new RegExp(prefix, "g"), "****");
|
|
2002
|
+
}
|
|
2003
|
+
return message;
|
|
2004
|
+
}
|
|
2005
|
+
ensureNotDisposed() {
|
|
2006
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
// src/providers/vercel/embedding-provider.ts
|
|
2011
|
+
var import_ai2 = require("ai");
|
|
2012
|
+
var import_openai_compatible2 = require("@ai-sdk/openai-compatible");
|
|
2013
|
+
var VercelEmbeddingProvider = class {
|
|
2014
|
+
id;
|
|
2015
|
+
model;
|
|
2016
|
+
dimensions;
|
|
2017
|
+
config;
|
|
2018
|
+
disposed = false;
|
|
2019
|
+
constructor(config, id) {
|
|
2020
|
+
this.id = id ?? "vercel-embedding";
|
|
2021
|
+
this.model = config.model;
|
|
2022
|
+
this.dimensions = config.dimensions;
|
|
2023
|
+
this.config = config;
|
|
2024
|
+
}
|
|
2025
|
+
async ensureReady() {
|
|
2026
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
2027
|
+
if (!this.config.apiKey) throw new Error("Embedding API key is required");
|
|
2028
|
+
if (!this.config.baseURL) throw new Error("Embedding base URL is required");
|
|
2029
|
+
try {
|
|
2030
|
+
const provider = this.createProvider();
|
|
2031
|
+
const result = await (0, import_ai2.embed)({
|
|
2032
|
+
model: provider.textEmbeddingModel(this.config.model),
|
|
2033
|
+
value: "test",
|
|
2034
|
+
abortSignal: AbortSignal.timeout(15e3)
|
|
2035
|
+
});
|
|
2036
|
+
if (!result.embedding?.length) throw new Error("Empty embedding result");
|
|
2037
|
+
} catch (error) {
|
|
2038
|
+
const msg = error instanceof Error ? error.message : "Embedding readiness failed";
|
|
2039
|
+
throw new Error(`Embedding provider not ready: ${this.redact(msg)}`);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
async dispose() {
|
|
2043
|
+
this.disposed = true;
|
|
2044
|
+
}
|
|
2045
|
+
async embedMany(texts, options) {
|
|
2046
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
2047
|
+
if (texts.length === 0) return [];
|
|
2048
|
+
const provider = this.createProvider();
|
|
2049
|
+
const result = await (0, import_ai2.embedMany)({
|
|
2050
|
+
model: provider.textEmbeddingModel(this.config.model),
|
|
2051
|
+
values: texts,
|
|
2052
|
+
abortSignal: options?.signal
|
|
2053
|
+
});
|
|
2054
|
+
return result.embeddings;
|
|
2055
|
+
}
|
|
2056
|
+
async embed(text, options) {
|
|
2057
|
+
if (this.disposed) throw new Error("Provider disposed");
|
|
2058
|
+
const provider = this.createProvider();
|
|
2059
|
+
const result = await (0, import_ai2.embed)({
|
|
2060
|
+
model: provider.textEmbeddingModel(this.config.model),
|
|
2061
|
+
value: text,
|
|
2062
|
+
abortSignal: options?.signal
|
|
2063
|
+
});
|
|
2064
|
+
return result.embedding;
|
|
2065
|
+
}
|
|
2066
|
+
async test() {
|
|
2067
|
+
try {
|
|
2068
|
+
const embedding = await this.embed("Hello, this is a test.");
|
|
2069
|
+
return { success: true, dimensions: embedding.length };
|
|
2070
|
+
} catch (error) {
|
|
2071
|
+
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
createProvider() {
|
|
2075
|
+
return (0, import_openai_compatible2.createOpenAICompatible)({
|
|
2076
|
+
name: this.config.providerName ?? "embedding-provider",
|
|
2077
|
+
apiKey: this.config.apiKey,
|
|
2078
|
+
baseURL: this.config.baseURL
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
redact(message) {
|
|
2082
|
+
if (this.config.apiKey?.length > 8) {
|
|
2083
|
+
return message.replace(new RegExp(this.config.apiKey.slice(0, 8), "g"), "****");
|
|
2084
|
+
}
|
|
2085
|
+
return message;
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2089
|
+
0 && (module.exports = {
|
|
2090
|
+
EMPTY_CAPABILITIES,
|
|
2091
|
+
MockSanqianSdk,
|
|
2092
|
+
SanqianEmbeddingProvider,
|
|
2093
|
+
SanqianProvider,
|
|
2094
|
+
SanqianRerankProvider,
|
|
2095
|
+
VercelEmbeddingProvider,
|
|
2096
|
+
VercelProvider,
|
|
2097
|
+
convertSdkStreamEvent,
|
|
2098
|
+
convertToolsForVercel,
|
|
2099
|
+
createActiveWorkTracker,
|
|
2100
|
+
createCategoryPermissionPolicy,
|
|
2101
|
+
createChatRuntimeBridge,
|
|
2102
|
+
createDefaultPermissionPolicy,
|
|
2103
|
+
createDisabledState,
|
|
2104
|
+
createEmbeddingSession,
|
|
2105
|
+
createMemoryConversationStore,
|
|
2106
|
+
createMemorySecretStore,
|
|
2107
|
+
createOutputOperationsManager,
|
|
2108
|
+
createPermissionGate,
|
|
2109
|
+
createSanqianAgentIdResolver,
|
|
2110
|
+
createSessionManager,
|
|
2111
|
+
createTaskPipeline,
|
|
2112
|
+
createToolRegistry,
|
|
2113
|
+
redactToolError,
|
|
2114
|
+
resolveAiFeaturesFromRuntime,
|
|
2115
|
+
validateOutputContent
|
|
2116
|
+
});
|
|
2117
|
+
//# sourceMappingURL=index.js.map
|