@alexkroman1/aai 1.3.2 → 1.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/.turbo/turbo-build.log +18 -14
- package/CHANGELOG.md +2 -0
- package/dist/_internal-types-3p3OJZPb.js +145 -0
- package/dist/anthropic-BrUCPKUc.js +10 -0
- package/dist/assemblyai-Cxg9eobY.js +18 -0
- package/dist/cartesia-DwDk2tEu.js +10 -0
- package/dist/host/_pipeline-test-fakes.d.ts +5 -5
- package/dist/host/pipeline-session.d.ts +5 -5
- package/dist/host/providers/resolve.d.ts +34 -0
- package/dist/host/providers/stt/assemblyai.d.ts +9 -18
- package/dist/host/providers/tts/cartesia.d.ts +11 -18
- package/dist/host/runtime-barrel.js +345 -42
- package/dist/host/runtime.d.ts +13 -9
- package/dist/index.js +2 -91
- package/dist/sdk/_internal-types.d.ts +27 -1
- package/dist/sdk/manifest-barrel.d.ts +2 -0
- package/dist/sdk/manifest-barrel.js +2 -2
- package/dist/sdk/manifest.d.ts +13 -2
- package/dist/sdk/protocol.d.ts +3 -3
- package/dist/sdk/protocol.js +1 -1
- package/dist/sdk/providers/llm/anthropic.d.ts +23 -0
- package/dist/sdk/providers/llm-barrel.d.ts +9 -0
- package/dist/sdk/providers/llm-barrel.js +2 -0
- package/dist/sdk/providers/stt/assemblyai.d.ts +30 -0
- package/dist/sdk/providers/stt-barrel.d.ts +9 -0
- package/dist/sdk/providers/stt-barrel.js +2 -0
- package/dist/sdk/providers/tts/cartesia.d.ts +23 -0
- package/dist/sdk/providers/tts-barrel.d.ts +9 -0
- package/dist/sdk/providers/tts-barrel.js +2 -0
- package/dist/sdk/providers.d.ts +59 -11
- package/dist/types-KUgezM6u.js +128 -0
- package/host/_pipeline-test-fakes.ts +6 -6
- package/host/integration/pipeline-reference.integration.test.ts +4 -4
- package/host/pipeline-session.ts +6 -6
- package/host/providers/providers.test-d.ts +19 -10
- package/host/providers/resolve.ts +87 -0
- package/host/providers/stt/assemblyai.test.ts +2 -2
- package/host/providers/stt/assemblyai.ts +25 -47
- package/host/providers/tts/cartesia.test.ts +2 -2
- package/host/providers/tts/cartesia.ts +43 -73
- package/host/runtime.ts +66 -39
- package/package.json +13 -7
- package/sdk/__snapshots__/exports.test.ts.snap +2 -0
- package/sdk/__snapshots__/schema-shapes.test.ts.snap +4 -0
- package/sdk/_internal-types.ts +28 -1
- package/sdk/define.test.ts +12 -10
- package/sdk/manifest-barrel.ts +2 -0
- package/sdk/manifest.test.ts +6 -3
- package/sdk/manifest.ts +26 -18
- package/sdk/providers/llm/anthropic.ts +31 -0
- package/sdk/providers/llm-barrel.ts +12 -0
- package/sdk/providers/stt/assemblyai.ts +38 -0
- package/sdk/providers/stt-barrel.ts +12 -0
- package/sdk/providers/tts/cartesia.ts +31 -0
- package/sdk/providers/tts-barrel.ts +12 -0
- package/sdk/providers.ts +81 -17
- package/dist/_internal-types-CoDTiBd1.js +0 -61
- package/dist/host/providers/llm.d.ts +0 -2
- package/dist/host/providers/stt-barrel.d.ts +0 -8
- package/dist/host/providers/stt-barrel.js +0 -92
- package/dist/host/providers/stt.d.ts +0 -2
- package/dist/host/providers/tts-barrel.d.ts +0 -8
- package/dist/host/providers/tts-barrel.js +0 -182
- package/dist/host/providers/tts.d.ts +0 -2
- package/dist/types-Cfx_4QDK.js +0 -39
- package/host/providers/llm.ts +0 -3
- package/host/providers/stt-barrel.ts +0 -13
- package/host/providers/stt.ts +0 -3
- package/host/providers/tts-barrel.ts +0 -13
- package/host/providers/tts.ts +0 -3
- /package/dist/{constants-BL3nvg4I.js → constants-C2nirZUI.js} +0 -0
package/dist/sdk/protocol.d.ts
CHANGED
|
@@ -63,10 +63,10 @@ export declare const SessionErrorCodeSchema: z.ZodEnum<{
|
|
|
63
63
|
internal: "internal";
|
|
64
64
|
audio: "audio";
|
|
65
65
|
tool: "tool";
|
|
66
|
-
connection: "connection";
|
|
67
66
|
stt: "stt";
|
|
68
67
|
llm: "llm";
|
|
69
68
|
tts: "tts";
|
|
69
|
+
connection: "connection";
|
|
70
70
|
protocol: "protocol";
|
|
71
71
|
}>;
|
|
72
72
|
/**
|
|
@@ -110,10 +110,10 @@ export declare const ClientEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
110
110
|
internal: "internal";
|
|
111
111
|
audio: "audio";
|
|
112
112
|
tool: "tool";
|
|
113
|
-
connection: "connection";
|
|
114
113
|
stt: "stt";
|
|
115
114
|
llm: "llm";
|
|
116
115
|
tts: "tts";
|
|
116
|
+
connection: "connection";
|
|
117
117
|
protocol: "protocol";
|
|
118
118
|
}>;
|
|
119
119
|
message: z.ZodString;
|
|
@@ -192,10 +192,10 @@ export declare const ServerMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
192
192
|
internal: "internal";
|
|
193
193
|
audio: "audio";
|
|
194
194
|
tool: "tool";
|
|
195
|
-
connection: "connection";
|
|
196
195
|
stt: "stt";
|
|
197
196
|
llm: "llm";
|
|
198
197
|
tts: "tts";
|
|
198
|
+
connection: "connection";
|
|
199
199
|
protocol: "protocol";
|
|
200
200
|
}>;
|
|
201
201
|
message: z.ZodString;
|
package/dist/sdk/protocol.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic LLM factory — returns a pure descriptor.
|
|
3
|
+
*
|
|
4
|
+
* Users call this in place of importing from `@ai-sdk/anthropic` directly,
|
|
5
|
+
* so agent bundles don't drag the Anthropic SDK into the guest sandbox
|
|
6
|
+
* (which has no `--allow-env` permission and would crash on the SDK's
|
|
7
|
+
* eager `ANTHROPIC_BASE_URL` read).
|
|
8
|
+
*
|
|
9
|
+
* The host-side resolver in `host/providers/resolve.ts` builds a real
|
|
10
|
+
* Vercel AI SDK `LanguageModel` from this descriptor during
|
|
11
|
+
* `createRuntime`, using `ANTHROPIC_API_KEY` from the agent's env.
|
|
12
|
+
*/
|
|
13
|
+
import type { LlmProvider } from "../../providers.ts";
|
|
14
|
+
export declare const ANTHROPIC_KIND: "anthropic";
|
|
15
|
+
export interface AnthropicOptions {
|
|
16
|
+
/** Anthropic model id, e.g. `"claude-haiku-4-5"`. */
|
|
17
|
+
model: string;
|
|
18
|
+
}
|
|
19
|
+
export type AnthropicProvider = LlmProvider & {
|
|
20
|
+
readonly kind: typeof ANTHROPIC_KIND;
|
|
21
|
+
readonly options: AnthropicOptions;
|
|
22
|
+
};
|
|
23
|
+
export declare function anthropic(opts: AnthropicOptions): AnthropicProvider;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@alexkroman1/aai/llm` subpath barrel.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports LLM provider factories. Users import from here instead of
|
|
5
|
+
* `@ai-sdk/anthropic` directly so the agent bundle stays free of eager
|
|
6
|
+
* env reads and other SDK side-effects.
|
|
7
|
+
*/
|
|
8
|
+
export type { LlmProvider } from "../providers.ts";
|
|
9
|
+
export * from "./llm/anthropic.ts";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AssemblyAI Universal-Streaming STT factory — returns a pure descriptor.
|
|
3
|
+
*
|
|
4
|
+
* The descriptor flows through the bundle → server → runtime pipeline
|
|
5
|
+
* without importing the `assemblyai` SDK. The host-side resolver in
|
|
6
|
+
* `host/providers/resolve.ts` turns it into an openable {@link SttOpener}
|
|
7
|
+
* during `createRuntime`.
|
|
8
|
+
*/
|
|
9
|
+
import type { SttProvider } from "../../providers.ts";
|
|
10
|
+
/** Kind tag recognised by the host-side resolver. */
|
|
11
|
+
export declare const ASSEMBLYAI_KIND: "assemblyai";
|
|
12
|
+
export interface AssemblyAIOptions {
|
|
13
|
+
/**
|
|
14
|
+
* Streaming speech model. Defaults to `"u3pro-rt"` (Universal-3 Pro
|
|
15
|
+
* Real-Time). Arbitrary strings are forwarded to the SDK unchanged.
|
|
16
|
+
*/
|
|
17
|
+
model?: "u3pro-rt" | string;
|
|
18
|
+
}
|
|
19
|
+
export type AssemblyAIProvider = SttProvider & {
|
|
20
|
+
readonly kind: typeof ASSEMBLYAI_KIND;
|
|
21
|
+
readonly options: AssemblyAIOptions;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Build an AssemblyAI STT descriptor.
|
|
25
|
+
*
|
|
26
|
+
* The API key is resolved host-side from the agent's env
|
|
27
|
+
* (`ASSEMBLYAI_API_KEY`); there is no factory-time key parameter, so the
|
|
28
|
+
* descriptor stays free of secrets and safe to serialize.
|
|
29
|
+
*/
|
|
30
|
+
export declare function assemblyAI(opts?: AssemblyAIOptions): AssemblyAIProvider;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@alexkroman1/aai/stt` subpath barrel.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the descriptor factory (`assemblyAI`) and the shared STT
|
|
5
|
+
* contract types. Importing this barrel does not pull in the `assemblyai`
|
|
6
|
+
* SDK — that happens only when the host resolver is invoked.
|
|
7
|
+
*/
|
|
8
|
+
export type { SttError, SttEvents, SttOpenOptions, SttProvider, SttSession } from "../providers.ts";
|
|
9
|
+
export * from "./stt/assemblyai.ts";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cartesia TTS factory — returns a pure descriptor.
|
|
3
|
+
*
|
|
4
|
+
* See `sdk/providers/stt/assemblyai.ts` for the descriptor/opener split;
|
|
5
|
+
* the host-side resolver in `host/providers/resolve.ts` turns this into an
|
|
6
|
+
* openable {@link TtsOpener} during `createRuntime` using the
|
|
7
|
+
* `CARTESIA_API_KEY` from the agent's env.
|
|
8
|
+
*/
|
|
9
|
+
import type { TtsProvider } from "../../providers.ts";
|
|
10
|
+
export declare const CARTESIA_KIND: "cartesia";
|
|
11
|
+
export interface CartesiaOptions {
|
|
12
|
+
/** Cartesia voice ID. Required. */
|
|
13
|
+
voice: string;
|
|
14
|
+
/** Model ID. Defaults to `"sonic-2"`. */
|
|
15
|
+
model?: string;
|
|
16
|
+
/** Spoken language hint. Defaults to `"en"`. */
|
|
17
|
+
language?: string;
|
|
18
|
+
}
|
|
19
|
+
export type CartesiaProvider = TtsProvider & {
|
|
20
|
+
readonly kind: typeof CARTESIA_KIND;
|
|
21
|
+
readonly options: CartesiaOptions;
|
|
22
|
+
};
|
|
23
|
+
export declare function cartesia(opts: CartesiaOptions): CartesiaProvider;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@alexkroman1/aai/tts` subpath barrel.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the descriptor factory (`cartesia`) and the shared TTS
|
|
5
|
+
* contract types. Does not pull in `@cartesia/cartesia-js` — the host
|
|
6
|
+
* resolver handles that at session start.
|
|
7
|
+
*/
|
|
8
|
+
export type { TtsError, TtsEvents, TtsOpenOptions, TtsProvider, TtsSession } from "../providers.ts";
|
|
9
|
+
export * from "./tts/cartesia.ts";
|
package/dist/sdk/providers.d.ts
CHANGED
|
@@ -1,18 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pluggable provider
|
|
3
|
-
* SDKs, plus the LLM provider type.
|
|
2
|
+
* Pluggable provider contracts.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
4
|
+
* **Two layers, strict boundary.**
|
|
5
|
+
*
|
|
6
|
+
* - The *descriptor* layer (`SttProvider` / `LlmProvider` / `TtsProvider`) is
|
|
7
|
+
* pure data — `{ kind, options }` objects returned by the user-facing
|
|
8
|
+
* factories (`assemblyAI(...)`, `anthropic(...)`, `cartesia(...)`). They
|
|
9
|
+
* are JSON-serializable, contain no functions, and can cross the CLI →
|
|
10
|
+
* server → guest boundary without evaluating any third-party SDK.
|
|
11
|
+
* They live in `sdk/` alongside `Manifest` and have zero Node-only deps.
|
|
12
|
+
*
|
|
13
|
+
* - The *openable* layer (`SttOpener` / `TtsOpener` + `SttSession` /
|
|
14
|
+
* `TtsSession`) is host-only. The host's internal
|
|
15
|
+
* `host/providers/resolve.ts` registry turns descriptors into openers
|
|
16
|
+
* during `createRuntime`, importing the concrete SDKs (`assemblyai`,
|
|
17
|
+
* `@cartesia/cartesia-js`, `@ai-sdk/anthropic`) only at that point.
|
|
18
|
+
* Only the openable layer talks to the network; descriptors never do.
|
|
19
|
+
*
|
|
20
|
+
* This split is load-bearing for the sandboxed deployment path: the guest
|
|
21
|
+
* Deno sandbox can import `@alexkroman1/aai/{stt,tts,llm}` without pulling
|
|
22
|
+
* in any AI-SDK code, which means no env reads (`ANTHROPIC_BASE_URL`, etc.)
|
|
23
|
+
* at bundle load — the exact failure mode that forced this refactor.
|
|
9
24
|
*/
|
|
10
|
-
import type { LanguageModel } from "ai";
|
|
11
25
|
/** Unsubscribe callback returned by `.on()` event subscriptions. */
|
|
12
26
|
export type Unsubscribe = () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Base shape for a provider descriptor. A `kind` tag + opaque `options`
|
|
29
|
+
* payload lets the host registry pick the right resolver and pass the
|
|
30
|
+
* caller's options through verbatim.
|
|
31
|
+
*/
|
|
32
|
+
export interface ProviderDescriptor<Kind extends string, Options> {
|
|
33
|
+
readonly kind: Kind;
|
|
34
|
+
readonly options: Options;
|
|
35
|
+
}
|
|
36
|
+
/** Descriptor for an STT provider. Returned by factories like `assemblyAI(...)`. */
|
|
37
|
+
export type SttProvider = ProviderDescriptor<string, Record<string, unknown>>;
|
|
38
|
+
/** Descriptor for an LLM provider. Returned by factories like `anthropic(...)`. */
|
|
39
|
+
export type LlmProvider = ProviderDescriptor<string, Record<string, unknown>>;
|
|
40
|
+
/** Descriptor for a TTS provider. Returned by factories like `cartesia(...)`. */
|
|
41
|
+
export type TtsProvider = ProviderDescriptor<string, Record<string, unknown>>;
|
|
42
|
+
/**
|
|
43
|
+
* Session mode derived from which provider triple is set.
|
|
44
|
+
*
|
|
45
|
+
* `parseManifest`, `toAgentConfig`, `createRuntime`, and the server's
|
|
46
|
+
* `IsolateConfigSchema` all use {@link assertProviderTriple} so there's
|
|
47
|
+
* one source of truth for the validation.
|
|
48
|
+
*/
|
|
49
|
+
export type SessionMode = "s2s" | "pipeline";
|
|
50
|
+
/**
|
|
51
|
+
* Enforce the all-or-nothing provider rule and return the derived mode.
|
|
52
|
+
*
|
|
53
|
+
* Pipeline mode requires STT, LLM, and TTS all set; S2S mode requires
|
|
54
|
+
* none of them. Anything in-between is a configuration error.
|
|
55
|
+
*/
|
|
56
|
+
export declare function assertProviderTriple(stt: unknown, llm: unknown, tts: unknown): SessionMode;
|
|
13
57
|
export interface SttError extends Error {
|
|
14
58
|
readonly code: "stt_connect_failed" | "stt_auth_failed" | "stt_stream_error";
|
|
15
59
|
}
|
|
60
|
+
/** Build an {@link SttError} with a typed `code`. Zero-dep helper so both sdk/ and host/ can use it. */
|
|
61
|
+
export declare function makeSttError(code: SttError["code"], message: string): SttError;
|
|
16
62
|
export type SttEvents = {
|
|
17
63
|
/** Interim transcript; drives barge-in detection. */
|
|
18
64
|
partial: (text: string) => void;
|
|
@@ -32,13 +78,16 @@ export interface SttOpenOptions {
|
|
|
32
78
|
sttPrompt?: string | undefined;
|
|
33
79
|
signal: AbortSignal;
|
|
34
80
|
}
|
|
35
|
-
|
|
81
|
+
/** Host-side openable STT provider — produced by `resolveStt(descriptor)`. */
|
|
82
|
+
export interface SttOpener {
|
|
36
83
|
readonly name: string;
|
|
37
84
|
open(opts: SttOpenOptions): Promise<SttSession>;
|
|
38
85
|
}
|
|
39
86
|
export interface TtsError extends Error {
|
|
40
87
|
readonly code: "tts_connect_failed" | "tts_auth_failed" | "tts_stream_error";
|
|
41
88
|
}
|
|
89
|
+
/** Build a {@link TtsError} with a typed `code`. Mirror of {@link makeSttError}. */
|
|
90
|
+
export declare function makeTtsError(code: TtsError["code"], message: string): TtsError;
|
|
42
91
|
export type TtsEvents = {
|
|
43
92
|
/** One PCM16 audio chunk. Orchestrator forwards to the client. */
|
|
44
93
|
audio: (pcm: Int16Array) => void;
|
|
@@ -62,9 +111,8 @@ export interface TtsOpenOptions {
|
|
|
62
111
|
apiKey: string;
|
|
63
112
|
signal: AbortSignal;
|
|
64
113
|
}
|
|
65
|
-
|
|
114
|
+
/** Host-side openable TTS provider — produced by `resolveTts(descriptor)`. */
|
|
115
|
+
export interface TtsOpener {
|
|
66
116
|
readonly name: string;
|
|
67
117
|
open(opts: TtsOpenOptions): Promise<TtsSession>;
|
|
68
118
|
}
|
|
69
|
-
/** LLM provider — Vercel AI SDK's `LanguageModel`; no wrapping. */
|
|
70
|
-
export type LlmProvider = LanguageModel;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region sdk/allowed-hosts.ts
|
|
3
|
+
/**
|
|
4
|
+
* Allowlist matching for outbound host validation.
|
|
5
|
+
*
|
|
6
|
+
* Used at deploy time (manifest validation) and at runtime (SSRF enforcement)
|
|
7
|
+
* to restrict which external hosts an agent is permitted to contact.
|
|
8
|
+
*
|
|
9
|
+
* Lives in sdk/ because it has zero Node.js dependencies and can run in any
|
|
10
|
+
* environment (browser, Deno, Node.js sandboxes).
|
|
11
|
+
*/
|
|
12
|
+
/** Private/special-use TLDs that must never appear in allowedHosts patterns. */
|
|
13
|
+
const BLOCKED_TLDS = [
|
|
14
|
+
"local",
|
|
15
|
+
"internal",
|
|
16
|
+
"localhost"
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Regex that matches an IPv4 address (four decimal octets separated by dots).
|
|
20
|
+
* Anchored so partial matches like "192.168.1.1.example.com" don't trigger it.
|
|
21
|
+
*/
|
|
22
|
+
const IPV4_RE = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
23
|
+
function fail(reason) {
|
|
24
|
+
return {
|
|
25
|
+
valid: false,
|
|
26
|
+
reason
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function checkStructural(pattern) {
|
|
30
|
+
if (pattern === "") return fail("Pattern must not be empty.");
|
|
31
|
+
if (pattern.includes("://")) return fail("Pattern must not include a protocol (e.g. remove 'https://').");
|
|
32
|
+
if (pattern.includes("/")) return fail("Pattern must not include a path component (remove '/').");
|
|
33
|
+
if (pattern.includes("?")) return fail("Pattern must not include a query string (remove '?').");
|
|
34
|
+
if (pattern.startsWith("[") || pattern.includes("::")) return fail("IP address literals are not allowed in allowedHosts patterns.");
|
|
35
|
+
if (pattern.includes(":")) return fail("Pattern must not include a port number (e.g. remove ':8080').");
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function checkWildcard(pattern) {
|
|
39
|
+
if (!pattern.includes("*")) return null;
|
|
40
|
+
if (pattern === "*" || pattern === "**") return fail("Bare wildcard '*' is not allowed. Use '*.example.com' to allow all subdomains.");
|
|
41
|
+
if (pattern.indexOf("*") !== 0 || pattern[1] !== ".") return fail("Wildcard '*' may only appear as the leading segment (e.g. '*.example.com').");
|
|
42
|
+
if (pattern.lastIndexOf("*") !== 0) return fail("Only a single leading wildcard segment is supported.");
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function checkHostPart(hostPart) {
|
|
46
|
+
if (IPV4_RE.test(hostPart)) return fail("IP address literals are not allowed in allowedHosts patterns.");
|
|
47
|
+
const tld = hostPart.split(".").at(-1)?.toLowerCase() ?? "";
|
|
48
|
+
if (BLOCKED_TLDS.includes(tld)) return fail(`Patterns ending in '.${tld}' are not allowed (private/special-use TLD).`);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Validate a single `allowedHosts` pattern at deploy time.
|
|
53
|
+
*
|
|
54
|
+
* Returns `{ valid: true }` for acceptable patterns or
|
|
55
|
+
* `{ valid: false; reason: string }` with a human-readable rejection reason.
|
|
56
|
+
*/
|
|
57
|
+
function validateAllowedHostPattern(pattern) {
|
|
58
|
+
const structural = checkStructural(pattern);
|
|
59
|
+
if (structural !== null) return structural;
|
|
60
|
+
const wildcard = checkWildcard(pattern);
|
|
61
|
+
if (wildcard !== null) return wildcard;
|
|
62
|
+
const hostCheck = checkHostPart(pattern.startsWith("*.") ? pattern.slice(2) : pattern);
|
|
63
|
+
if (hostCheck !== null) return hostCheck;
|
|
64
|
+
return { valid: true };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Test whether `hostname` matches any pattern in `patterns`.
|
|
68
|
+
*
|
|
69
|
+
* - Exact match is case-insensitive; trailing dots on the hostname are stripped.
|
|
70
|
+
* - Wildcard pattern `*.example.com` matches any hostname ending with
|
|
71
|
+
* `.example.com` (one or more labels), but does NOT match `example.com` itself.
|
|
72
|
+
* - A port suffix on `hostname` (e.g. `api.example.com:8080`) is stripped before
|
|
73
|
+
* matching.
|
|
74
|
+
* - Returns `false` when `patterns` is empty.
|
|
75
|
+
*/
|
|
76
|
+
function matchesAllowedHost(hostname, patterns) {
|
|
77
|
+
if (patterns.length === 0) return false;
|
|
78
|
+
const portIndex = hostname.lastIndexOf(":");
|
|
79
|
+
let host = portIndex !== -1 && !hostname.includes("[") ? hostname.slice(0, portIndex) : hostname;
|
|
80
|
+
host = host.toLowerCase().replace(/\.$/, "");
|
|
81
|
+
for (const pattern of patterns) {
|
|
82
|
+
const p = pattern.toLowerCase();
|
|
83
|
+
if (p.startsWith("*.")) {
|
|
84
|
+
const suffix = p.slice(1);
|
|
85
|
+
if (host.endsWith(suffix) && host.length > suffix.length) return true;
|
|
86
|
+
} else if (host === p) return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region sdk/types.ts
|
|
92
|
+
/**
|
|
93
|
+
* Core type definitions for the AAI agent SDK.
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* Default system prompt used when `systemPrompt` is not provided.
|
|
97
|
+
*
|
|
98
|
+
* Optimized for voice-first interactions: short sentences, no visual
|
|
99
|
+
* formatting, confident tone, and concise answers.
|
|
100
|
+
*/
|
|
101
|
+
const DEFAULT_SYSTEM_PROMPT = `\
|
|
102
|
+
You are AAI, a helpful AI assistant.
|
|
103
|
+
|
|
104
|
+
Voice-First Rules:
|
|
105
|
+
- Optimize for natural speech. Avoid jargon unless central to the answer. \
|
|
106
|
+
Use short, punchy sentences.
|
|
107
|
+
- Never mention "search results," "sources," or "the provided text." \
|
|
108
|
+
Speak as if the knowledge is your own.
|
|
109
|
+
- No visual formatting. Do not say "bullet point," "bold," or "bracketed one." \
|
|
110
|
+
If you need to list items, say "First," "Next," and "Finally."
|
|
111
|
+
- Start with the most important information. No introductory filler.
|
|
112
|
+
- Be concise. Keep answers to 1-3 sentences. For complex topics, provide a high-level summary.
|
|
113
|
+
- Be confident. Avoid hedging phrases like "It seems that" or "I believe."
|
|
114
|
+
- If you don't have enough information, say so directly rather than guessing.
|
|
115
|
+
- Never use exclamation points. Keep your tone calm and conversational.`;
|
|
116
|
+
/** Default greeting spoken when a session starts. */
|
|
117
|
+
const DEFAULT_GREETING = "Hey there. I'm a voice assistant. What can I help you with?";
|
|
118
|
+
/** @internal Zod schema for {@link BuiltinTool}. Exported for reuse in internal schemas. */
|
|
119
|
+
const BuiltinToolSchema = z.enum([
|
|
120
|
+
"web_search",
|
|
121
|
+
"visit_webpage",
|
|
122
|
+
"fetch_json",
|
|
123
|
+
"run_code"
|
|
124
|
+
]);
|
|
125
|
+
/** @internal Zod schema for {@link ToolChoice}. Exported for reuse in internal schemas. */
|
|
126
|
+
const ToolChoiceSchema = z.enum(["auto", "required"]);
|
|
127
|
+
//#endregion
|
|
128
|
+
export { matchesAllowedHost as a, ToolChoiceSchema as i, DEFAULT_GREETING as n, validateAllowedHostPattern as o, DEFAULT_SYSTEM_PROMPT as r, BuiltinToolSchema as t };
|
|
@@ -19,12 +19,12 @@ import { createNanoEvents, type Emitter } from "nanoevents";
|
|
|
19
19
|
import { vi } from "vitest";
|
|
20
20
|
import type {
|
|
21
21
|
SttEvents,
|
|
22
|
+
SttOpener,
|
|
22
23
|
SttOpenOptions,
|
|
23
|
-
SttProvider,
|
|
24
24
|
SttSession,
|
|
25
25
|
TtsEvents,
|
|
26
|
+
TtsOpener,
|
|
26
27
|
TtsOpenOptions,
|
|
27
|
-
TtsProvider,
|
|
28
28
|
TtsSession,
|
|
29
29
|
} from "../sdk/providers.ts";
|
|
30
30
|
|
|
@@ -43,7 +43,7 @@ export type FakeSttSession = SttSession & {
|
|
|
43
43
|
): void;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
export type FakeSttProvider =
|
|
46
|
+
export type FakeSttProvider = SttOpener & {
|
|
47
47
|
/** The most recently opened session, or undefined if `open()` hasn't been called. */
|
|
48
48
|
last(): FakeSttSession | undefined;
|
|
49
49
|
readonly sessions: FakeSttSession[];
|
|
@@ -107,7 +107,7 @@ export type FakeTtsSession = TtsSession & {
|
|
|
107
107
|
): void;
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
export type FakeTtsProvider =
|
|
110
|
+
export type FakeTtsProvider = TtsOpener & {
|
|
111
111
|
/** The most recently opened session, or undefined if `open()` hasn't been called. */
|
|
112
112
|
last(): FakeTtsSession | undefined;
|
|
113
113
|
readonly sessions: FakeTtsSession[];
|
|
@@ -175,7 +175,7 @@ export function createFakeTtsProvider(
|
|
|
175
175
|
export function createFailingSttProvider(
|
|
176
176
|
code: "stt_connect_failed" | "stt_auth_failed" | "stt_stream_error",
|
|
177
177
|
message: string,
|
|
178
|
-
):
|
|
178
|
+
): SttOpener {
|
|
179
179
|
return {
|
|
180
180
|
name: "failing-stt",
|
|
181
181
|
async open(): Promise<SttSession> {
|
|
@@ -192,7 +192,7 @@ export function createFailingSttProvider(
|
|
|
192
192
|
export function createFailingTtsProvider(
|
|
193
193
|
code: "tts_connect_failed" | "tts_auth_failed" | "tts_stream_error",
|
|
194
194
|
message: string,
|
|
195
|
-
):
|
|
195
|
+
): TtsOpener {
|
|
196
196
|
return {
|
|
197
197
|
name: "failing-tts",
|
|
198
198
|
async open(): Promise<TtsSession> {
|
|
@@ -27,8 +27,8 @@ import { describe, expect, test } from "vitest";
|
|
|
27
27
|
import type { AgentConfig, ExecuteTool } from "../../sdk/_internal-types.ts";
|
|
28
28
|
import type { ClientEvent, ClientSink } from "../../sdk/protocol.ts";
|
|
29
29
|
import { createPipelineSession } from "../pipeline-session.ts";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
30
|
+
import { openAssemblyAI } from "../providers/stt/assemblyai.ts";
|
|
31
|
+
import { openCartesia } from "../providers/tts/cartesia.ts";
|
|
32
32
|
import { consoleLogger } from "../runtime-config.ts";
|
|
33
33
|
|
|
34
34
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -90,9 +90,9 @@ describe.skipIf(!envReady)("pipeline integration — reference stack", () => {
|
|
|
90
90
|
agentConfig,
|
|
91
91
|
toolSchemas: [],
|
|
92
92
|
executeTool,
|
|
93
|
-
stt:
|
|
93
|
+
stt: openAssemblyAI({ model: "u3pro-rt" }),
|
|
94
94
|
llm: openai("gpt-4o-mini"),
|
|
95
|
-
tts:
|
|
95
|
+
tts: openCartesia({ voice: "694f9389-aac1-45b6-b726-9d9369183238" }),
|
|
96
96
|
// biome-ignore lint/style/noNonNullAssertion: envReady guard ensures presence
|
|
97
97
|
sttApiKey: process.env.ASSEMBLYAI_API_KEY!,
|
|
98
98
|
// biome-ignore lint/style/noNonNullAssertion: envReady guard ensures presence
|
package/host/pipeline-session.ts
CHANGED
|
@@ -14,10 +14,10 @@ import { DEFAULT_STT_SAMPLE_RATE, PIPELINE_FLUSH_TIMEOUT_MS } from "../sdk/const
|
|
|
14
14
|
import type { ClientSink, SessionErrorCode } from "../sdk/protocol.ts";
|
|
15
15
|
import type {
|
|
16
16
|
SttError,
|
|
17
|
-
|
|
17
|
+
SttOpener,
|
|
18
18
|
SttSession,
|
|
19
19
|
TtsError,
|
|
20
|
-
|
|
20
|
+
TtsOpener,
|
|
21
21
|
TtsSession,
|
|
22
22
|
Unsubscribe,
|
|
23
23
|
} from "../sdk/providers.ts";
|
|
@@ -45,12 +45,12 @@ export interface PipelineSessionOptions {
|
|
|
45
45
|
toolGuidance?: readonly string[] | undefined;
|
|
46
46
|
/** Function to invoke tools by name. */
|
|
47
47
|
executeTool: ExecuteTool;
|
|
48
|
-
/** STT
|
|
49
|
-
stt:
|
|
48
|
+
/** STT opener (resolved from an {@link SttProvider} descriptor). */
|
|
49
|
+
stt: SttOpener;
|
|
50
50
|
/** LLM provider (Vercel AI SDK `LanguageModel`). */
|
|
51
51
|
llm: LanguageModel;
|
|
52
|
-
/** TTS
|
|
53
|
-
tts:
|
|
52
|
+
/** TTS opener (resolved from a {@link TtsProvider} descriptor). */
|
|
53
|
+
tts: TtsOpener;
|
|
54
54
|
/** STT API key. */
|
|
55
55
|
sttApiKey: string;
|
|
56
56
|
/** TTS API key. */
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
-
import type { LanguageModel } from "ai";
|
|
3
2
|
import { expectTypeOf, test } from "vitest";
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import type {
|
|
4
|
+
LlmProvider,
|
|
5
|
+
SttEvents,
|
|
6
|
+
SttOpener,
|
|
7
|
+
SttProvider,
|
|
8
|
+
SttSession,
|
|
9
|
+
TtsEvents,
|
|
10
|
+
TtsProvider,
|
|
11
|
+
TtsSession,
|
|
12
|
+
Unsubscribe,
|
|
13
|
+
} from "../../sdk/providers.ts";
|
|
7
14
|
|
|
8
|
-
test("
|
|
9
|
-
expectTypeOf<SttProvider
|
|
15
|
+
test("Descriptors are { kind, options } data", () => {
|
|
16
|
+
expectTypeOf<SttProvider>().toMatchTypeOf<{ kind: string; options: Record<string, unknown> }>();
|
|
17
|
+
expectTypeOf<LlmProvider>().toMatchTypeOf<{ kind: string; options: Record<string, unknown> }>();
|
|
18
|
+
expectTypeOf<TtsProvider>().toMatchTypeOf<{ kind: string; options: Record<string, unknown> }>();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("SttOpener.open returns Promise<SttSession>", () => {
|
|
22
|
+
expectTypeOf<SttOpener["open"]>().returns.toEqualTypeOf<Promise<SttSession>>();
|
|
10
23
|
});
|
|
11
24
|
|
|
12
25
|
test("SttEvents.partial takes a string", () => {
|
|
@@ -21,10 +34,6 @@ test("TtsEvents.audio takes Int16Array", () => {
|
|
|
21
34
|
expectTypeOf<TtsEvents["audio"]>().parameters.toEqualTypeOf<[Int16Array]>();
|
|
22
35
|
});
|
|
23
36
|
|
|
24
|
-
test("LlmProvider is Vercel AI SDK's LanguageModel", () => {
|
|
25
|
-
expectTypeOf<LlmProvider>().toEqualTypeOf<LanguageModel>();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
37
|
test("Stt/Tts on() returns Unsubscribe", () => {
|
|
29
38
|
expectTypeOf<SttSession["on"]>().returns.toEqualTypeOf<Unsubscribe>();
|
|
30
39
|
expectTypeOf<TtsSession["on"]>().returns.toEqualTypeOf<Unsubscribe>();
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
+
/**
|
|
3
|
+
* Descriptor → concrete-provider resolution (host-only).
|
|
4
|
+
*
|
|
5
|
+
* User code (and the server, after extracting config from a bundled agent)
|
|
6
|
+
* holds `SttProvider` / `LlmProvider` / `TtsProvider` **descriptors** —
|
|
7
|
+
* plain `{ kind, options }` data. At session start the runtime calls the
|
|
8
|
+
* resolvers here to turn each descriptor into its openable / callable
|
|
9
|
+
* host-side counterpart, importing the third-party SDK only at that point.
|
|
10
|
+
*
|
|
11
|
+
* The guest sandbox never imports these functions, which is how the agent
|
|
12
|
+
* bundle stays free of `@ai-sdk/anthropic` / `assemblyai` /
|
|
13
|
+
* `@cartesia/cartesia-js`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
17
|
+
import type { LanguageModel } from "ai";
|
|
18
|
+
import { ANTHROPIC_KIND, type AnthropicOptions } from "../../sdk/providers/llm/anthropic.ts";
|
|
19
|
+
import { ASSEMBLYAI_KIND, type AssemblyAIOptions } from "../../sdk/providers/stt/assemblyai.ts";
|
|
20
|
+
import { CARTESIA_KIND, type CartesiaOptions } from "../../sdk/providers/tts/cartesia.ts";
|
|
21
|
+
import type {
|
|
22
|
+
LlmProvider,
|
|
23
|
+
SttOpener,
|
|
24
|
+
SttProvider,
|
|
25
|
+
TtsOpener,
|
|
26
|
+
TtsProvider,
|
|
27
|
+
} from "../../sdk/providers.ts";
|
|
28
|
+
import { openAssemblyAI } from "./stt/assemblyai.ts";
|
|
29
|
+
import { openCartesia } from "./tts/cartesia.ts";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Look up a provider API key: agent env first (set via `aai secret put` or
|
|
33
|
+
* `.env`), then the host's `process.env` as a fallback for self-hosted mode.
|
|
34
|
+
* Returns `""` if neither has it — the caller decides whether that's fatal.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveApiKey(envVar: string, env: Record<string, string>): string {
|
|
37
|
+
return env[envVar] ?? process.env[envVar] ?? "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Resolve an {@link SttProvider} descriptor into a host-side opener. */
|
|
41
|
+
export function resolveStt(descriptor: SttProvider): SttOpener {
|
|
42
|
+
switch (descriptor.kind) {
|
|
43
|
+
case ASSEMBLYAI_KIND:
|
|
44
|
+
return openAssemblyAI(descriptor.options as unknown as AssemblyAIOptions);
|
|
45
|
+
default:
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Unknown STT provider kind: "${descriptor.kind}". Supported: ${ASSEMBLYAI_KIND}.`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Resolve a {@link TtsProvider} descriptor into a host-side opener. */
|
|
53
|
+
export function resolveTts(descriptor: TtsProvider): TtsOpener {
|
|
54
|
+
switch (descriptor.kind) {
|
|
55
|
+
case CARTESIA_KIND:
|
|
56
|
+
return openCartesia(descriptor.options as unknown as CartesiaOptions);
|
|
57
|
+
default:
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Unknown TTS provider kind: "${descriptor.kind}". Supported: ${CARTESIA_KIND}.`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve an {@link LlmProvider} descriptor into a Vercel AI SDK
|
|
66
|
+
* {@link LanguageModel}.
|
|
67
|
+
*
|
|
68
|
+
* The API key is pulled from the agent's env (e.g. `ANTHROPIC_API_KEY`).
|
|
69
|
+
* Missing keys throw here — the pipeline session would fail on first
|
|
70
|
+
* `streamText` call otherwise, and the error is clearer at construction.
|
|
71
|
+
*/
|
|
72
|
+
export function resolveLlm(descriptor: LlmProvider, env: Record<string, string>): LanguageModel {
|
|
73
|
+
switch (descriptor.kind) {
|
|
74
|
+
case ANTHROPIC_KIND: {
|
|
75
|
+
const options = descriptor.options as unknown as AnthropicOptions;
|
|
76
|
+
const apiKey = resolveApiKey("ANTHROPIC_API_KEY", env);
|
|
77
|
+
if (!apiKey) {
|
|
78
|
+
throw new Error("Anthropic LLM: missing API key. Set ANTHROPIC_API_KEY in the agent env.");
|
|
79
|
+
}
|
|
80
|
+
return createAnthropic({ apiKey })(options.model);
|
|
81
|
+
}
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Unknown LLM provider kind: "${descriptor.kind}". Supported: ${ANTHROPIC_KIND}.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import type { TurnEvent } from "assemblyai";
|
|
8
8
|
import { describe, expect, test, vi } from "vitest";
|
|
9
9
|
import { flush } from "../../_test-utils.ts";
|
|
10
|
-
import { type AssemblyAISession,
|
|
10
|
+
import { type AssemblyAISession, openAssemblyAI } from "./assemblyai.ts";
|
|
11
11
|
|
|
12
12
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
|
|
@@ -66,7 +66,7 @@ describe("assemblyAI STT adapter — fixture replay", () => {
|
|
|
66
66
|
await readFile(join(here, "fixtures/assemblyai/basic-turn.json"), "utf8"),
|
|
67
67
|
) as Record<string, unknown>[];
|
|
68
68
|
|
|
69
|
-
const provider =
|
|
69
|
+
const provider = openAssemblyAI({ model: "u3pro-rt" });
|
|
70
70
|
const controller = new AbortController();
|
|
71
71
|
const session = (await provider.open({
|
|
72
72
|
sampleRate: 16_000,
|