@terminaluse/vercel-ai-sdk-provider 0.3.0 → 0.4.1
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 +7 -1
- package/dist/index.mjs +210 -80
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { TerminalUse } from "@terminaluse/sdk";
|
|
2
1
|
import { LanguageModel } from "ai";
|
|
2
|
+
import { TerminalUse } from "@terminaluse/sdk";
|
|
3
3
|
|
|
4
4
|
//#region src/provider.d.ts
|
|
5
5
|
interface TerminalUseProviderConfig {
|
|
@@ -19,6 +19,12 @@ interface TerminalUseProvider {
|
|
|
19
19
|
interface TerminalUseProviderOptions {
|
|
20
20
|
/** Task ID to send messages to (required) */
|
|
21
21
|
taskId: string;
|
|
22
|
+
/** Skip task event dispatch and only stream task output */
|
|
23
|
+
skipSend?: boolean;
|
|
24
|
+
/** Optional absolute URL override for stream endpoint */
|
|
25
|
+
streamUrl?: string;
|
|
26
|
+
/** Optional headers for stream requests (e.g. bridge auth) */
|
|
27
|
+
streamHeaders?: Record<string, string>;
|
|
22
28
|
/**
|
|
23
29
|
* Optional override for the outbound task event. If omitted, provider sends
|
|
24
30
|
* a text event derived from the latest prompt message (default behavior).
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TerminalUseClient } from "@terminaluse/sdk";
|
|
2
1
|
import { UnsupportedFunctionalityError } from "ai";
|
|
3
2
|
import { createEventSourceResponseHandler, getFromApi } from "@ai-sdk/provider-utils";
|
|
4
3
|
import { z } from "zod";
|
|
4
|
+
import { TerminalUseClient } from "@terminaluse/sdk";
|
|
5
5
|
|
|
6
6
|
//#region src/ndjson-stream.ts
|
|
7
7
|
/**
|
|
@@ -50,6 +50,14 @@ const finishPartSchema = z.object({
|
|
|
50
50
|
totalUsage: streamUsageSchema,
|
|
51
51
|
metadata: streamMetadataSchema
|
|
52
52
|
});
|
|
53
|
+
const handlerCompletePartSchema = z.object({
|
|
54
|
+
type: z.literal("handler-complete"),
|
|
55
|
+
eventId: z.string(),
|
|
56
|
+
taskId: z.string(),
|
|
57
|
+
success: z.boolean(),
|
|
58
|
+
durationMs: z.number().int().nullable().optional(),
|
|
59
|
+
error: z.string().nullable().optional()
|
|
60
|
+
});
|
|
53
61
|
const textStartPartSchema = z.object({
|
|
54
62
|
type: z.literal("text-start"),
|
|
55
63
|
id: z.string(),
|
|
@@ -127,6 +135,7 @@ const textStreamPartSchema = z.discriminatedUnion("type", [
|
|
|
127
135
|
startStepPartSchema,
|
|
128
136
|
finishStepPartSchema,
|
|
129
137
|
finishPartSchema,
|
|
138
|
+
handlerCompletePartSchema,
|
|
130
139
|
textStartPartSchema,
|
|
131
140
|
textDeltaPartSchema,
|
|
132
141
|
textEndPartSchema,
|
|
@@ -145,10 +154,14 @@ const textStreamPartSchema = z.discriminatedUnion("type", [
|
|
|
145
154
|
* Uses @ai-sdk/provider-utils for SSE parsing.
|
|
146
155
|
*/
|
|
147
156
|
async function* createTaskEventGenerator(config, taskId, options = {}) {
|
|
148
|
-
const { signal } = options;
|
|
157
|
+
const { signal, streamUrl, streamHeaders } = options;
|
|
158
|
+
const headers = {
|
|
159
|
+
...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {},
|
|
160
|
+
...streamHeaders ?? {}
|
|
161
|
+
};
|
|
149
162
|
const { value: responseStream } = await getFromApi({
|
|
150
|
-
url: `${config.baseURL}/tasks/${encodeURIComponent(taskId)}/stream`,
|
|
151
|
-
headers
|
|
163
|
+
url: streamUrl ?? `${config.baseURL}/tasks/${encodeURIComponent(taskId)}/stream`,
|
|
164
|
+
headers,
|
|
152
165
|
abortSignal: signal,
|
|
153
166
|
successfulResponseHandler: createEventSourceResponseHandler(textStreamPartSchema),
|
|
154
167
|
failedResponseHandler: async ({ response }) => {
|
|
@@ -175,9 +188,16 @@ async function* createTaskEventGenerator(config, taskId, options = {}) {
|
|
|
175
188
|
* Creates a ReadableStream that transforms v2 TextStreamPart events to AI SDK v3 format.
|
|
176
189
|
* Implements a thin passthrough adapter with minimal transformation logic.
|
|
177
190
|
*/
|
|
178
|
-
function createTerminalUseTransformStream(config, taskId, signal) {
|
|
179
|
-
const
|
|
191
|
+
function createTerminalUseTransformStream(config, taskId, signal, arg4, arg5) {
|
|
192
|
+
const { streamOverride, closeConfig } = resolveStreamArgs(arg4, arg5);
|
|
193
|
+
const eventGenerator = createTaskEventGenerator(config, taskId, {
|
|
194
|
+
signal,
|
|
195
|
+
streamUrl: streamOverride?.streamUrl,
|
|
196
|
+
streamHeaders: streamOverride?.streamHeaders
|
|
197
|
+
});
|
|
180
198
|
let emittedStreamStart = false;
|
|
199
|
+
const pendingParts = [];
|
|
200
|
+
const trackedParts = createStreamedPartTracker();
|
|
181
201
|
return new ReadableStream({
|
|
182
202
|
async pull(controller) {
|
|
183
203
|
if (!emittedStreamStart) {
|
|
@@ -188,6 +208,13 @@ function createTerminalUseTransformStream(config, taskId, signal) {
|
|
|
188
208
|
});
|
|
189
209
|
return;
|
|
190
210
|
}
|
|
211
|
+
if (pendingParts.length > 0) {
|
|
212
|
+
const pendingPart = pendingParts.shift();
|
|
213
|
+
if (pendingPart) {
|
|
214
|
+
controller.enqueue(pendingPart);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
191
218
|
try {
|
|
192
219
|
while (true) {
|
|
193
220
|
const { value, done } = await eventGenerator.next();
|
|
@@ -196,14 +223,16 @@ function createTerminalUseTransformStream(config, taskId, signal) {
|
|
|
196
223
|
return;
|
|
197
224
|
}
|
|
198
225
|
const event = value;
|
|
199
|
-
const
|
|
200
|
-
if (event
|
|
201
|
-
|
|
226
|
+
const transformedParts = transformEvent(event, taskId, trackedParts);
|
|
227
|
+
if (shouldCloseStream(event, closeConfig)) {
|
|
228
|
+
for (const part of transformedParts) controller.enqueue(part);
|
|
202
229
|
controller.close();
|
|
203
230
|
return;
|
|
204
231
|
}
|
|
205
|
-
if (
|
|
206
|
-
|
|
232
|
+
if (transformedParts.length > 0) {
|
|
233
|
+
const [firstPart, ...restParts] = transformedParts;
|
|
234
|
+
controller.enqueue(firstPart);
|
|
235
|
+
if (restParts.length > 0) pendingParts.push(...restParts);
|
|
207
236
|
return;
|
|
208
237
|
}
|
|
209
238
|
}
|
|
@@ -219,19 +248,19 @@ function createTerminalUseTransformStream(config, taskId, signal) {
|
|
|
219
248
|
}
|
|
220
249
|
/**
|
|
221
250
|
* Transforms a single v2 TextStreamPart event to AI SDK v3 LanguageModelV3StreamPart.
|
|
222
|
-
* Returns
|
|
251
|
+
* Returns an array to support synthesizing start parts for orphan deltas.
|
|
223
252
|
*/
|
|
224
|
-
function transformEvent(event, taskId) {
|
|
253
|
+
function transformEvent(event, taskId, trackedParts) {
|
|
225
254
|
switch (event.type) {
|
|
226
|
-
case "start": return
|
|
227
|
-
case "start-step": return {
|
|
255
|
+
case "start": return [];
|
|
256
|
+
case "start-step": return [{
|
|
228
257
|
type: "response-metadata",
|
|
229
258
|
id: taskId,
|
|
230
259
|
timestamp: /* @__PURE__ */ new Date(),
|
|
231
260
|
modelId: "terminaluse-agent"
|
|
232
|
-
};
|
|
233
|
-
case "finish-step": return
|
|
234
|
-
case "finish": return {
|
|
261
|
+
}];
|
|
262
|
+
case "finish-step": return [];
|
|
263
|
+
case "finish": return [{
|
|
235
264
|
type: "finish",
|
|
236
265
|
finishReason: {
|
|
237
266
|
unified: event.finishReason,
|
|
@@ -250,76 +279,145 @@ function transformEvent(event, taskId) {
|
|
|
250
279
|
reasoning: void 0
|
|
251
280
|
}
|
|
252
281
|
}
|
|
253
|
-
};
|
|
254
|
-
case "
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
case "
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
282
|
+
}];
|
|
283
|
+
case "handler-complete": return [];
|
|
284
|
+
case "text-start":
|
|
285
|
+
if (trackedParts.text.open.has(event.id)) return [];
|
|
286
|
+
trackedParts.text.open.add(event.id);
|
|
287
|
+
return [{
|
|
288
|
+
type: "text-start",
|
|
289
|
+
id: event.id
|
|
290
|
+
}];
|
|
291
|
+
case "text-delta": {
|
|
292
|
+
const deltaPart = {
|
|
293
|
+
type: "text-delta",
|
|
294
|
+
id: event.id,
|
|
295
|
+
delta: event.text
|
|
296
|
+
};
|
|
297
|
+
if (trackedParts.text.open.has(event.id)) return [deltaPart];
|
|
298
|
+
if (trackedParts.text.open.size > 0) return [];
|
|
299
|
+
trackedParts.text.open.add(event.id);
|
|
300
|
+
return [{
|
|
301
|
+
type: "text-start",
|
|
302
|
+
id: event.id
|
|
303
|
+
}, deltaPart];
|
|
304
|
+
}
|
|
305
|
+
case "text-end":
|
|
306
|
+
if (!trackedParts.text.open.has(event.id)) return [];
|
|
307
|
+
trackedParts.text.open.delete(event.id);
|
|
308
|
+
return [{
|
|
309
|
+
type: "text-end",
|
|
310
|
+
id: event.id
|
|
311
|
+
}];
|
|
312
|
+
case "reasoning-start":
|
|
313
|
+
if (trackedParts.reasoning.open.has(event.id)) return [];
|
|
314
|
+
trackedParts.reasoning.open.add(event.id);
|
|
315
|
+
return [{
|
|
316
|
+
type: "reasoning-start",
|
|
317
|
+
id: event.id
|
|
318
|
+
}];
|
|
319
|
+
case "reasoning-delta": {
|
|
320
|
+
const deltaPart = {
|
|
321
|
+
type: "reasoning-delta",
|
|
322
|
+
id: event.id,
|
|
323
|
+
delta: event.text
|
|
324
|
+
};
|
|
325
|
+
if (trackedParts.reasoning.open.has(event.id)) return [deltaPart];
|
|
326
|
+
if (trackedParts.reasoning.open.size > 0) return [];
|
|
327
|
+
trackedParts.reasoning.open.add(event.id);
|
|
328
|
+
return [{
|
|
329
|
+
type: "reasoning-start",
|
|
330
|
+
id: event.id
|
|
331
|
+
}, deltaPart];
|
|
332
|
+
}
|
|
333
|
+
case "reasoning-end":
|
|
334
|
+
if (!trackedParts.reasoning.open.has(event.id)) return [];
|
|
335
|
+
trackedParts.reasoning.open.delete(event.id);
|
|
336
|
+
return [{
|
|
337
|
+
type: "reasoning-end",
|
|
338
|
+
id: event.id
|
|
339
|
+
}];
|
|
340
|
+
case "tool-input-start":
|
|
341
|
+
if (trackedParts.toolInput.open.has(event.id)) return [];
|
|
342
|
+
trackedParts.toolInput.open.add(event.id);
|
|
343
|
+
return [{
|
|
344
|
+
type: "tool-input-start",
|
|
345
|
+
id: event.id,
|
|
346
|
+
toolName: event.toolName,
|
|
347
|
+
providerExecuted: true,
|
|
348
|
+
dynamic: true
|
|
349
|
+
}];
|
|
350
|
+
case "tool-input-delta":
|
|
351
|
+
if (!trackedParts.toolInput.open.has(event.id)) return [];
|
|
352
|
+
return [{
|
|
353
|
+
type: "tool-input-delta",
|
|
354
|
+
id: event.id,
|
|
355
|
+
delta: event.delta
|
|
356
|
+
}];
|
|
357
|
+
case "tool-input-end":
|
|
358
|
+
if (!trackedParts.toolInput.open.has(event.id)) return [];
|
|
359
|
+
trackedParts.toolInput.open.delete(event.id);
|
|
360
|
+
return [{
|
|
361
|
+
type: "tool-input-end",
|
|
362
|
+
id: event.id
|
|
363
|
+
}];
|
|
364
|
+
case "tool-call": return [{
|
|
297
365
|
type: "tool-call",
|
|
298
366
|
toolCallId: event.toolCallId,
|
|
299
367
|
toolName: event.toolName,
|
|
300
368
|
input: JSON.stringify(event.input),
|
|
301
369
|
providerExecuted: true,
|
|
302
370
|
dynamic: true
|
|
303
|
-
};
|
|
371
|
+
}];
|
|
304
372
|
case "tool-result": {
|
|
305
373
|
const result = event.output ?? "";
|
|
306
|
-
return {
|
|
374
|
+
return [{
|
|
307
375
|
type: "tool-result",
|
|
308
376
|
toolCallId: event.toolCallId,
|
|
309
377
|
toolName: event.toolName,
|
|
310
378
|
result
|
|
311
|
-
};
|
|
379
|
+
}];
|
|
312
380
|
}
|
|
313
|
-
case "error": return {
|
|
381
|
+
case "error": return [{
|
|
314
382
|
type: "error",
|
|
315
383
|
error: event.error
|
|
316
|
-
};
|
|
317
|
-
default: return
|
|
384
|
+
}];
|
|
385
|
+
default: return [];
|
|
318
386
|
}
|
|
319
387
|
}
|
|
388
|
+
function createStreamedPartTracker() {
|
|
389
|
+
return {
|
|
390
|
+
text: createSegmentTracker(),
|
|
391
|
+
reasoning: createSegmentTracker(),
|
|
392
|
+
toolInput: createSegmentTracker()
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function createSegmentTracker() {
|
|
396
|
+
return { open: /* @__PURE__ */ new Set() };
|
|
397
|
+
}
|
|
398
|
+
function shouldCloseStream(event, closeConfig) {
|
|
399
|
+
if (event.type === "error") return true;
|
|
400
|
+
if (closeConfig.closeMode === "legacy") return event.type === "finish";
|
|
401
|
+
return event.type === "handler-complete" && event.eventId === closeConfig.eventId;
|
|
402
|
+
}
|
|
403
|
+
function resolveStreamArgs(arg4, arg5) {
|
|
404
|
+
if (isStreamCloseConfig(arg4)) return {
|
|
405
|
+
streamOverride: void 0,
|
|
406
|
+
closeConfig: arg4
|
|
407
|
+
};
|
|
408
|
+
return {
|
|
409
|
+
streamOverride: arg4,
|
|
410
|
+
closeConfig: arg5 ?? { closeMode: "legacy" }
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function isStreamCloseConfig(value) {
|
|
414
|
+
if (!value || typeof value !== "object") return false;
|
|
415
|
+
return "closeMode" in value;
|
|
416
|
+
}
|
|
320
417
|
|
|
321
418
|
//#endregion
|
|
322
419
|
//#region src/provider.ts
|
|
420
|
+
const HANDLER_COMPLETE_CLOSE_CAPABILITY = "handler-complete-close-v1";
|
|
323
421
|
/**
|
|
324
422
|
* Creates a custom AI SDK provider for TerminalUse agents.
|
|
325
423
|
*
|
|
@@ -372,7 +470,7 @@ function createTerminalUseProvider(config) {
|
|
|
372
470
|
const { prompt, providerOptions, abortSignal } = options;
|
|
373
471
|
const tuOptions = providerOptions?.terminaluse;
|
|
374
472
|
if (!tuOptions?.taskId) throw new Error("taskId is required. Create a task via /api/tasks first.");
|
|
375
|
-
const { taskId } = tuOptions;
|
|
473
|
+
const { taskId, skipSend = false } = tuOptions;
|
|
376
474
|
const explicitEvent = tuOptions.event;
|
|
377
475
|
const defaultEventContent = (() => {
|
|
378
476
|
const userContent = prompt.at(-1)?.content;
|
|
@@ -386,22 +484,54 @@ function createTerminalUseProvider(config) {
|
|
|
386
484
|
text: textContent || ""
|
|
387
485
|
};
|
|
388
486
|
})();
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
487
|
+
if (!skipSend) {
|
|
488
|
+
const requestBody = {
|
|
489
|
+
task_id: taskId,
|
|
490
|
+
content: explicitEvent?.content ?? defaultEventContent,
|
|
491
|
+
...explicitEvent?.idempotencyKey ? { idempotency_key: explicitEvent.idempotencyKey } : {},
|
|
492
|
+
...typeof explicitEvent?.persistMessage === "boolean" ? { persist_message: explicitEvent.persistMessage } : {}
|
|
493
|
+
};
|
|
494
|
+
let sendEventResponse;
|
|
495
|
+
try {
|
|
496
|
+
sendEventResponse = await client.tasks.sendEvent(requestBody);
|
|
497
|
+
} catch (error) {
|
|
498
|
+
throw new Error(`Failed to send message: ${error instanceof Error ? error.message : String(error)}`);
|
|
499
|
+
}
|
|
500
|
+
const closeConfig = resolveStreamCloseConfig(sendEventResponse);
|
|
501
|
+
return { stream: typeof tuOptions.streamUrl === "string" || tuOptions.streamHeaders && Object.keys(tuOptions.streamHeaders).length > 0 ? createTerminalUseTransformStream(tuConfig, taskId, abortSignal, {
|
|
502
|
+
streamUrl: tuOptions.streamUrl,
|
|
503
|
+
streamHeaders: tuOptions.streamHeaders
|
|
504
|
+
}, closeConfig) : createTerminalUseTransformStream(tuConfig, taskId, abortSignal, closeConfig) };
|
|
399
505
|
}
|
|
400
|
-
return { stream: createTerminalUseTransformStream(tuConfig, taskId, abortSignal
|
|
506
|
+
return { stream: typeof tuOptions.streamUrl === "string" || tuOptions.streamHeaders && Object.keys(tuOptions.streamHeaders).length > 0 ? createTerminalUseTransformStream(tuConfig, taskId, abortSignal, {
|
|
507
|
+
streamUrl: tuOptions.streamUrl,
|
|
508
|
+
streamHeaders: tuOptions.streamHeaders
|
|
509
|
+
}) : createTerminalUseTransformStream(tuConfig, taskId, abortSignal) };
|
|
401
510
|
}
|
|
402
511
|
};
|
|
403
512
|
} };
|
|
404
513
|
}
|
|
514
|
+
function resolveStreamCloseConfig(response) {
|
|
515
|
+
const eventId = getStringProperty(response, "id");
|
|
516
|
+
const streamCapabilities = getStringArrayProperty(response, "streamCapabilities") ?? getStringArrayProperty(response, "stream_capabilities");
|
|
517
|
+
if (eventId && streamCapabilities?.includes(HANDLER_COMPLETE_CLOSE_CAPABILITY)) return {
|
|
518
|
+
closeMode: "handler-complete",
|
|
519
|
+
eventId
|
|
520
|
+
};
|
|
521
|
+
return { closeMode: "legacy" };
|
|
522
|
+
}
|
|
523
|
+
function getStringProperty(value, key) {
|
|
524
|
+
if (!value || typeof value !== "object") return;
|
|
525
|
+
const maybeValue = value[key];
|
|
526
|
+
return typeof maybeValue === "string" ? maybeValue : void 0;
|
|
527
|
+
}
|
|
528
|
+
function getStringArrayProperty(value, key) {
|
|
529
|
+
if (!value || typeof value !== "object") return;
|
|
530
|
+
const maybeArray = value[key];
|
|
531
|
+
if (!Array.isArray(maybeArray)) return;
|
|
532
|
+
if (!maybeArray.every((entry) => typeof entry === "string")) return;
|
|
533
|
+
return maybeArray;
|
|
534
|
+
}
|
|
405
535
|
|
|
406
536
|
//#endregion
|
|
407
537
|
export { createTerminalUseProvider };
|