@terminaluse/vercel-ai-sdk-provider 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/index.d.mts +27 -1
- package/dist/index.mjs +105 -37
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -36,9 +36,39 @@ const result = await streamText({
|
|
|
36
36
|
| Option | Type | Description |
|
|
37
37
|
|--------|------|-------------|
|
|
38
38
|
| `taskId` | `string` | Task ID to send messages to (required) |
|
|
39
|
+
| `event` | `object` | Optional explicit task event override (`content`, `persistMessage`, `idempotencyKey`) |
|
|
39
40
|
|
|
40
41
|
Tasks must be created separately using the [`@terminaluse/sdk`](https://www.npmjs.com/package/@terminaluse/sdk).
|
|
41
42
|
|
|
43
|
+
### Sending data events
|
|
44
|
+
|
|
45
|
+
By default, the provider sends a text event derived from the last prompt message.
|
|
46
|
+
If you need to send a structured task event (for example, AskUserQuestion answers),
|
|
47
|
+
pass `providerOptions.terminaluse.event`:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
await streamText({
|
|
51
|
+
model: terminaluse.agent('namespace/agent-name'),
|
|
52
|
+
messages: [],
|
|
53
|
+
providerOptions: {
|
|
54
|
+
terminaluse: {
|
|
55
|
+
taskId,
|
|
56
|
+
event: {
|
|
57
|
+
content: {
|
|
58
|
+
type: 'data',
|
|
59
|
+
data: {
|
|
60
|
+
type: 'ask_user_answer',
|
|
61
|
+
answers: { 'Question?': 'Answer' },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
persistMessage: false,
|
|
65
|
+
idempotencyKey: 'optional-idempotency-key',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
42
72
|
## Conversation Continuity
|
|
43
73
|
|
|
44
74
|
To maintain conversation history across multiple interactions, create a task once
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LanguageModel } from "ai";
|
|
2
|
+
import { TerminalUse } from "@terminaluse/sdk";
|
|
2
3
|
|
|
3
4
|
//#region src/provider.d.ts
|
|
4
5
|
interface TerminalUseProviderConfig {
|
|
@@ -18,6 +19,31 @@ interface TerminalUseProvider {
|
|
|
18
19
|
interface TerminalUseProviderOptions {
|
|
19
20
|
/** Task ID to send messages to (required) */
|
|
20
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>;
|
|
28
|
+
/**
|
|
29
|
+
* Optional override for the outbound task event. If omitted, provider sends
|
|
30
|
+
* a text event derived from the latest prompt message (default behavior).
|
|
31
|
+
*/
|
|
32
|
+
event?: TerminalUseProviderTaskEventOptions;
|
|
33
|
+
}
|
|
34
|
+
interface TerminalUseProviderTaskEventOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Explicit event content to send. Supports text and data task events.
|
|
37
|
+
*/
|
|
38
|
+
content: NonNullable<TerminalUse.CreateTaskEventRequest['content']>;
|
|
39
|
+
/**
|
|
40
|
+
* Whether TerminalUse should persist this event as a message.
|
|
41
|
+
*/
|
|
42
|
+
persistMessage?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Optional idempotency key for retry-safe event delivery.
|
|
45
|
+
*/
|
|
46
|
+
idempotencyKey?: string;
|
|
21
47
|
}
|
|
22
48
|
/**
|
|
23
49
|
* Creates a custom AI SDK provider for TerminalUse agents.
|
|
@@ -48,4 +74,4 @@ interface TerminalUseProviderOptions {
|
|
|
48
74
|
*/
|
|
49
75
|
declare function createTerminalUseProvider(config: TerminalUseProviderConfig): TerminalUseProvider;
|
|
50
76
|
//#endregion
|
|
51
|
-
export { type TerminalUseProvider, type TerminalUseProviderConfig, type TerminalUseProviderOptions, createTerminalUseProvider };
|
|
77
|
+
export { type TerminalUseProvider, type TerminalUseProviderConfig, type TerminalUseProviderOptions, type TerminalUseProviderTaskEventOptions, createTerminalUseProvider };
|
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
import { UnsupportedFunctionalityError } from "ai";
|
|
2
2
|
import { createEventSourceResponseHandler, getFromApi } from "@ai-sdk/provider-utils";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { TerminalUseClient } from "@terminaluse/sdk";
|
|
4
5
|
|
|
5
|
-
//#region src/http.ts
|
|
6
|
-
/**
|
|
7
|
-
* Fetch wrapper with TerminalUse authentication headers.
|
|
8
|
-
*/
|
|
9
|
-
async function terminaluseFetch(config, path, options = {}) {
|
|
10
|
-
const headers = new Headers(options.headers);
|
|
11
|
-
if (config.apiKey) headers.set("Authorization", `Bearer ${config.apiKey}`);
|
|
12
|
-
if (!headers.has("Content-Type") && options.body) headers.set("Content-Type", "application/json");
|
|
13
|
-
return fetch(`${config.baseURL}${path}`, {
|
|
14
|
-
...options,
|
|
15
|
-
headers
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
//#endregion
|
|
20
6
|
//#region src/ndjson-stream.ts
|
|
21
7
|
/**
|
|
22
8
|
* SSE streaming utility for TerminalUse task events.
|
|
@@ -64,6 +50,14 @@ const finishPartSchema = z.object({
|
|
|
64
50
|
totalUsage: streamUsageSchema,
|
|
65
51
|
metadata: streamMetadataSchema
|
|
66
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
|
+
});
|
|
67
61
|
const textStartPartSchema = z.object({
|
|
68
62
|
type: z.literal("text-start"),
|
|
69
63
|
id: z.string(),
|
|
@@ -141,6 +135,7 @@ const textStreamPartSchema = z.discriminatedUnion("type", [
|
|
|
141
135
|
startStepPartSchema,
|
|
142
136
|
finishStepPartSchema,
|
|
143
137
|
finishPartSchema,
|
|
138
|
+
handlerCompletePartSchema,
|
|
144
139
|
textStartPartSchema,
|
|
145
140
|
textDeltaPartSchema,
|
|
146
141
|
textEndPartSchema,
|
|
@@ -159,10 +154,14 @@ const textStreamPartSchema = z.discriminatedUnion("type", [
|
|
|
159
154
|
* Uses @ai-sdk/provider-utils for SSE parsing.
|
|
160
155
|
*/
|
|
161
156
|
async function* createTaskEventGenerator(config, taskId, options = {}) {
|
|
162
|
-
const { signal } = options;
|
|
157
|
+
const { signal, streamUrl, streamHeaders } = options;
|
|
158
|
+
const headers = {
|
|
159
|
+
...config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {},
|
|
160
|
+
...streamHeaders ?? {}
|
|
161
|
+
};
|
|
163
162
|
const { value: responseStream } = await getFromApi({
|
|
164
|
-
url: `${config.baseURL}/tasks/${encodeURIComponent(taskId)}/stream`,
|
|
165
|
-
headers
|
|
163
|
+
url: streamUrl ?? `${config.baseURL}/tasks/${encodeURIComponent(taskId)}/stream`,
|
|
164
|
+
headers,
|
|
166
165
|
abortSignal: signal,
|
|
167
166
|
successfulResponseHandler: createEventSourceResponseHandler(textStreamPartSchema),
|
|
168
167
|
failedResponseHandler: async ({ response }) => {
|
|
@@ -189,8 +188,13 @@ async function* createTaskEventGenerator(config, taskId, options = {}) {
|
|
|
189
188
|
* Creates a ReadableStream that transforms v2 TextStreamPart events to AI SDK v3 format.
|
|
190
189
|
* Implements a thin passthrough adapter with minimal transformation logic.
|
|
191
190
|
*/
|
|
192
|
-
function createTerminalUseTransformStream(config, taskId, signal) {
|
|
193
|
-
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
|
+
});
|
|
194
198
|
let emittedStreamStart = false;
|
|
195
199
|
return new ReadableStream({
|
|
196
200
|
async pull(controller) {
|
|
@@ -211,7 +215,7 @@ function createTerminalUseTransformStream(config, taskId, signal) {
|
|
|
211
215
|
}
|
|
212
216
|
const event = value;
|
|
213
217
|
const transformed = transformEvent(event, taskId);
|
|
214
|
-
if (event
|
|
218
|
+
if (shouldCloseStream(event, closeConfig)) {
|
|
215
219
|
if (transformed) controller.enqueue(transformed);
|
|
216
220
|
controller.close();
|
|
217
221
|
return;
|
|
@@ -265,6 +269,7 @@ function transformEvent(event, taskId) {
|
|
|
265
269
|
}
|
|
266
270
|
}
|
|
267
271
|
};
|
|
272
|
+
case "handler-complete": return null;
|
|
268
273
|
case "text-start": return {
|
|
269
274
|
type: "text-start",
|
|
270
275
|
id: event.id
|
|
@@ -331,9 +336,29 @@ function transformEvent(event, taskId) {
|
|
|
331
336
|
default: return null;
|
|
332
337
|
}
|
|
333
338
|
}
|
|
339
|
+
function shouldCloseStream(event, closeConfig) {
|
|
340
|
+
if (event.type === "error") return true;
|
|
341
|
+
if (closeConfig.closeMode === "legacy") return event.type === "finish";
|
|
342
|
+
return event.type === "handler-complete" && event.eventId === closeConfig.eventId;
|
|
343
|
+
}
|
|
344
|
+
function resolveStreamArgs(arg4, arg5) {
|
|
345
|
+
if (isStreamCloseConfig(arg4)) return {
|
|
346
|
+
streamOverride: void 0,
|
|
347
|
+
closeConfig: arg4
|
|
348
|
+
};
|
|
349
|
+
return {
|
|
350
|
+
streamOverride: arg4,
|
|
351
|
+
closeConfig: arg5 ?? { closeMode: "legacy" }
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function isStreamCloseConfig(value) {
|
|
355
|
+
if (!value || typeof value !== "object") return false;
|
|
356
|
+
return "closeMode" in value;
|
|
357
|
+
}
|
|
334
358
|
|
|
335
359
|
//#endregion
|
|
336
360
|
//#region src/provider.ts
|
|
361
|
+
const HANDLER_COMPLETE_CLOSE_CAPABILITY = "handler-complete-close-v1";
|
|
337
362
|
/**
|
|
338
363
|
* Creates a custom AI SDK provider for TerminalUse agents.
|
|
339
364
|
*
|
|
@@ -366,6 +391,10 @@ function createTerminalUseProvider(config) {
|
|
|
366
391
|
baseURL: config.baseURL,
|
|
367
392
|
apiKey: config.apiKey
|
|
368
393
|
};
|
|
394
|
+
const client = new TerminalUseClient({
|
|
395
|
+
environment: config.baseURL,
|
|
396
|
+
bearerAuth: { token: config.apiKey }
|
|
397
|
+
});
|
|
369
398
|
return { agent(agentName) {
|
|
370
399
|
return {
|
|
371
400
|
specificationVersion: "v3",
|
|
@@ -382,29 +411,68 @@ function createTerminalUseProvider(config) {
|
|
|
382
411
|
const { prompt, providerOptions, abortSignal } = options;
|
|
383
412
|
const tuOptions = providerOptions?.terminaluse;
|
|
384
413
|
if (!tuOptions?.taskId) throw new Error("taskId is required. Create a task via /api/tasks first.");
|
|
385
|
-
const { taskId } = tuOptions;
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
414
|
+
const { taskId, skipSend = false } = tuOptions;
|
|
415
|
+
const explicitEvent = tuOptions.event;
|
|
416
|
+
const defaultEventContent = (() => {
|
|
417
|
+
const userContent = prompt.at(-1)?.content;
|
|
418
|
+
let textContent;
|
|
419
|
+
if (Array.isArray(userContent)) {
|
|
420
|
+
const textPart = userContent.find((c) => c.type === "text");
|
|
421
|
+
if (textPart && textPart.type === "text") textContent = textPart.text;
|
|
422
|
+
} else if (typeof userContent === "string") textContent = userContent;
|
|
423
|
+
return {
|
|
395
424
|
type: "text",
|
|
396
425
|
text: textContent || ""
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
if (!
|
|
400
|
-
const
|
|
401
|
-
|
|
426
|
+
};
|
|
427
|
+
})();
|
|
428
|
+
if (!skipSend) {
|
|
429
|
+
const requestBody = {
|
|
430
|
+
task_id: taskId,
|
|
431
|
+
content: explicitEvent?.content ?? defaultEventContent,
|
|
432
|
+
...explicitEvent?.idempotencyKey ? { idempotency_key: explicitEvent.idempotencyKey } : {},
|
|
433
|
+
...typeof explicitEvent?.persistMessage === "boolean" ? { persist_message: explicitEvent.persistMessage } : {}
|
|
434
|
+
};
|
|
435
|
+
let sendEventResponse;
|
|
436
|
+
try {
|
|
437
|
+
sendEventResponse = await client.tasks.sendEvent(requestBody);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
throw new Error(`Failed to send message: ${error instanceof Error ? error.message : String(error)}`);
|
|
440
|
+
}
|
|
441
|
+
const closeConfig = resolveStreamCloseConfig(sendEventResponse);
|
|
442
|
+
return { stream: typeof tuOptions.streamUrl === "string" || tuOptions.streamHeaders && Object.keys(tuOptions.streamHeaders).length > 0 ? createTerminalUseTransformStream(tuConfig, taskId, abortSignal, {
|
|
443
|
+
streamUrl: tuOptions.streamUrl,
|
|
444
|
+
streamHeaders: tuOptions.streamHeaders
|
|
445
|
+
}, closeConfig) : createTerminalUseTransformStream(tuConfig, taskId, abortSignal, closeConfig) };
|
|
402
446
|
}
|
|
403
|
-
return { stream: createTerminalUseTransformStream(tuConfig, taskId, abortSignal
|
|
447
|
+
return { stream: typeof tuOptions.streamUrl === "string" || tuOptions.streamHeaders && Object.keys(tuOptions.streamHeaders).length > 0 ? createTerminalUseTransformStream(tuConfig, taskId, abortSignal, {
|
|
448
|
+
streamUrl: tuOptions.streamUrl,
|
|
449
|
+
streamHeaders: tuOptions.streamHeaders
|
|
450
|
+
}) : createTerminalUseTransformStream(tuConfig, taskId, abortSignal) };
|
|
404
451
|
}
|
|
405
452
|
};
|
|
406
453
|
} };
|
|
407
454
|
}
|
|
455
|
+
function resolveStreamCloseConfig(response) {
|
|
456
|
+
const eventId = getStringProperty(response, "id");
|
|
457
|
+
const streamCapabilities = getStringArrayProperty(response, "streamCapabilities") ?? getStringArrayProperty(response, "stream_capabilities");
|
|
458
|
+
if (eventId && streamCapabilities?.includes(HANDLER_COMPLETE_CLOSE_CAPABILITY)) return {
|
|
459
|
+
closeMode: "handler-complete",
|
|
460
|
+
eventId
|
|
461
|
+
};
|
|
462
|
+
return { closeMode: "legacy" };
|
|
463
|
+
}
|
|
464
|
+
function getStringProperty(value, key) {
|
|
465
|
+
if (!value || typeof value !== "object") return;
|
|
466
|
+
const maybeValue = value[key];
|
|
467
|
+
return typeof maybeValue === "string" ? maybeValue : void 0;
|
|
468
|
+
}
|
|
469
|
+
function getStringArrayProperty(value, key) {
|
|
470
|
+
if (!value || typeof value !== "object") return;
|
|
471
|
+
const maybeArray = value[key];
|
|
472
|
+
if (!Array.isArray(maybeArray)) return;
|
|
473
|
+
if (!maybeArray.every((entry) => typeof entry === "string")) return;
|
|
474
|
+
return maybeArray;
|
|
475
|
+
}
|
|
408
476
|
|
|
409
477
|
//#endregion
|
|
410
478
|
export { createTerminalUseProvider };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terminaluse/vercel-ai-sdk-provider",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Vercel AI SDK provider for TerminalUse agents",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,11 +31,13 @@
|
|
|
31
31
|
"lint": "biome lint .",
|
|
32
32
|
"format": "biome format --write .",
|
|
33
33
|
"check": "biome check --write .",
|
|
34
|
-
"
|
|
34
|
+
"check:publish-manifest": "node ./scripts/check-publish-manifest.mjs",
|
|
35
|
+
"prepublishOnly": "bun run check:publish-manifest && bun run build"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@ai-sdk/provider": "^3.0.0",
|
|
38
39
|
"@ai-sdk/provider-utils": "^4.0.0",
|
|
40
|
+
"@terminaluse/sdk": "^0.7.0",
|
|
39
41
|
"zod": "^3.24.0"
|
|
40
42
|
},
|
|
41
43
|
"peerDependencies": {
|