@alexkroman1/aai 1.7.0 → 1.8.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 +11 -9
- package/CHANGELOG.md +16 -0
- package/dist/{_internal-types-CrnTi9Ew.js → _internal-types-CfOAbK6V.js} +22 -35
- package/dist/constants-y68COEGj.js +29 -0
- package/dist/host/_base64.d.ts +2 -0
- package/dist/host/_mock-ws.d.ts +0 -61
- package/dist/host/_pipeline-test-fakes.d.ts +7 -4
- package/dist/host/_run-code.d.ts +0 -25
- package/dist/host/_runtime-conformance.d.ts +3 -34
- package/dist/host/memory-vector.d.ts +0 -11
- package/dist/host/providers/resolve-kv.d.ts +0 -7
- package/dist/host/providers/resolve-vector.d.ts +0 -8
- package/dist/host/providers/stt/assemblyai.d.ts +0 -14
- package/dist/host/providers/stt/deepgram.d.ts +2 -14
- package/dist/host/providers/stt/soniox.d.ts +0 -22
- package/dist/host/providers/tts/rime.d.ts +10 -31
- package/dist/host/runtime-barrel.js +628 -642
- package/dist/host/runtime-config.d.ts +9 -6
- package/dist/host/runtime.d.ts +3 -0
- package/dist/host/to-vercel-tools.d.ts +3 -33
- package/dist/host/transports/openai-realtime-transport.d.ts +43 -0
- package/dist/host/unstorage-kv.d.ts +0 -26
- package/dist/index.js +3 -3
- package/dist/openai-realtime-cjPAHMMx.js +10 -0
- package/dist/sdk/_internal-types.d.ts +6 -55
- package/dist/sdk/allowed-hosts.d.ts +4 -3
- package/dist/sdk/constants.d.ts +4 -29
- package/dist/sdk/define.d.ts +7 -4
- package/dist/sdk/kv.d.ts +13 -37
- package/dist/sdk/manifest-barrel.js +1 -1
- package/dist/sdk/manifest.d.ts +8 -2
- package/dist/sdk/protocol.js +1 -1
- package/dist/sdk/providers/s2s/openai-realtime.d.ts +17 -0
- package/dist/sdk/providers/s2s-barrel.d.ts +9 -0
- package/dist/sdk/providers/s2s-barrel.js +2 -0
- package/dist/sdk/providers/tts/rime.d.ts +1 -1
- package/dist/sdk/providers.d.ts +6 -2
- package/dist/sdk/types.d.ts +7 -1
- package/dist/{types-KUgezM6u.js → types-DOWVZhb9.js} +1 -7
- package/dist/{ws-upgrade-BeOQ7fXL.js → ws-upgrade-CG8-by1n.js} +2 -3
- package/host/_base64.ts +9 -0
- package/host/_mock-ws.ts +0 -65
- package/host/_pipeline-test-fakes.ts +19 -31
- package/host/_run-code.ts +10 -53
- package/host/_runtime-conformance.ts +3 -44
- package/host/_test-utils.ts +20 -42
- package/host/builtin-tools.test.ts +127 -222
- package/host/builtin-tools.ts +6 -10
- package/host/cleanup.test.ts +30 -73
- package/host/integration/pipeline-reference.integration.test.ts +12 -17
- package/host/integration.test.ts +0 -7
- package/host/memory-vector.test.ts +3 -1
- package/host/memory-vector.ts +16 -21
- package/host/pinecone-vector.test.ts +14 -17
- package/host/pinecone-vector.ts +10 -19
- package/host/providers/providers.test-d.ts +5 -3
- package/host/providers/resolve-kv.ts +23 -41
- package/host/providers/resolve-vector.ts +3 -12
- package/host/providers/resolve.test.ts +15 -28
- package/host/providers/resolve.ts +24 -24
- package/host/providers/stt/assemblyai.test.ts +2 -14
- package/host/providers/stt/assemblyai.ts +12 -35
- package/host/providers/stt/deepgram.test.ts +23 -83
- package/host/providers/stt/deepgram.ts +15 -40
- package/host/providers/stt/elevenlabs.test.ts +26 -38
- package/host/providers/stt/elevenlabs.ts +10 -9
- package/host/providers/stt/soniox.test.ts +35 -85
- package/host/providers/stt/soniox.ts +8 -53
- package/host/providers/tts/cartesia.test.ts +19 -58
- package/host/providers/tts/cartesia.ts +36 -66
- package/host/providers/tts/rime.test.ts +12 -38
- package/host/providers/tts/rime.ts +23 -86
- package/host/runtime-config.test.ts +9 -9
- package/host/runtime-config.ts +16 -22
- package/host/runtime.test.ts +111 -73
- package/host/runtime.ts +138 -86
- package/host/s2s.test.ts +92 -191
- package/host/s2s.ts +56 -53
- package/host/server-shutdown.test.ts +9 -30
- package/host/server.test.ts +2 -13
- package/host/server.ts +85 -100
- package/host/session-core.test.ts +15 -30
- package/host/session-core.ts +10 -13
- package/host/session-prompt.test.ts +1 -5
- package/host/to-vercel-tools.test.ts +53 -72
- package/host/to-vercel-tools.ts +9 -39
- package/host/tool-executor.test.ts +25 -51
- package/host/tool-executor.ts +18 -12
- package/host/transports/openai-realtime-transport.test.ts +371 -0
- package/host/transports/openai-realtime-transport.ts +319 -0
- package/host/transports/pipeline-transport.test.ts +125 -298
- package/host/transports/pipeline-transport.ts +20 -68
- package/host/transports/s2s-transport-fixtures.test.ts +31 -92
- package/host/transports/s2s-transport.test.ts +65 -134
- package/host/transports/s2s-transport.ts +15 -43
- package/host/transports/types.test.ts +4 -8
- package/host/unstorage-kv.test.ts +3 -2
- package/host/unstorage-kv.ts +5 -35
- package/host/ws-handler.test.ts +72 -176
- package/host/ws-handler.ts +6 -12
- package/package.json +6 -1
- package/sdk/__snapshots__/exports.test.ts.snap +7 -0
- package/sdk/__snapshots__/schema-shapes.test.ts.snap +1 -0
- package/sdk/_internal-types.test.ts +6 -9
- package/sdk/_internal-types.ts +16 -57
- package/sdk/_test-matchers.ts +25 -15
- package/sdk/allowed-hosts.test.ts +50 -114
- package/sdk/allowed-hosts.ts +8 -14
- package/sdk/constants.ts +5 -52
- package/sdk/define.test.ts +7 -6
- package/sdk/define.ts +7 -3
- package/sdk/exports.test.ts +6 -1
- package/sdk/kv.ts +13 -37
- package/sdk/manifest.test-d.ts +5 -0
- package/sdk/manifest.test.ts +61 -9
- package/sdk/manifest.ts +11 -11
- package/sdk/protocol-compat.test.ts +66 -98
- package/sdk/protocol-snapshot.test.ts +2 -16
- package/sdk/protocol.test.ts +13 -22
- package/sdk/providers/s2s/openai-realtime.ts +36 -0
- package/sdk/providers/s2s-barrel.ts +12 -0
- package/sdk/providers/tts/rime.ts +1 -1
- package/sdk/providers.ts +24 -5
- package/sdk/schema-alignment.test.ts +25 -73
- package/sdk/schema-shapes.test.ts +1 -29
- package/sdk/system-prompt.test.ts +0 -1
- package/sdk/system-prompt.ts +17 -19
- package/sdk/types-inference.test.ts +10 -36
- package/sdk/types.ts +7 -0
- package/sdk/ws-upgrade.test.ts +24 -23
- package/sdk/ws-upgrade.ts +2 -3
- package/tsdown.config.ts +8 -11
- package/dist/constants-C2nirZUI.js +0 -54
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
+
/** OpenAI Realtime S2S descriptor — host transport resolves at session start. */
|
|
3
|
+
|
|
4
|
+
import type { S2sProvider } from "../../providers.ts";
|
|
5
|
+
|
|
6
|
+
export const OPENAI_REALTIME_KIND = "openai-realtime" as const;
|
|
7
|
+
|
|
8
|
+
export type OpenaiRealtimeVoice =
|
|
9
|
+
| "alloy"
|
|
10
|
+
| "ash"
|
|
11
|
+
| "ballad"
|
|
12
|
+
| "cedar"
|
|
13
|
+
| "coral"
|
|
14
|
+
| "echo"
|
|
15
|
+
| "marin"
|
|
16
|
+
| "sage"
|
|
17
|
+
| "shimmer"
|
|
18
|
+
| "verse";
|
|
19
|
+
|
|
20
|
+
export type OpenaiRealtimeOptions = {
|
|
21
|
+
/** Realtime model identifier. Default applied by the host (currently `"gpt-realtime-2"`). */
|
|
22
|
+
model?: string;
|
|
23
|
+
/** TTS voice. Default applied by the host (currently `"alloy"`). */
|
|
24
|
+
voice?: OpenaiRealtimeVoice;
|
|
25
|
+
/** Override the WebSocket base URL (testing/proxy). */
|
|
26
|
+
url?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type OpenaiRealtimeProvider = S2sProvider & {
|
|
30
|
+
readonly kind: typeof OPENAI_REALTIME_KIND;
|
|
31
|
+
readonly options: OpenaiRealtimeOptions;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function openaiRealtime(opts: OpenaiRealtimeOptions = {}): OpenaiRealtimeProvider {
|
|
35
|
+
return { kind: OPENAI_REALTIME_KIND, options: { ...opts } };
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
+
/**
|
|
3
|
+
* `@alexkroman1/aai/s2s` subpath barrel.
|
|
4
|
+
*
|
|
5
|
+
* Re-exports S2S descriptor factories. Importing this barrel does not
|
|
6
|
+
* pull in any provider SDK — the host resolver handles that at session
|
|
7
|
+
* start.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type { S2sProvider } from "../providers.ts";
|
|
11
|
+
// biome-ignore lint/performance/noReExportAll: subpath barrel
|
|
12
|
+
export * from "./s2s/openai-realtime.ts";
|
|
@@ -29,7 +29,7 @@ export interface RimeOptions {
|
|
|
29
29
|
* Rime model ID. Defaults to `"mistv2"` (Rime's most compatible model).
|
|
30
30
|
* Common values: `"mistv2"`, `"arcana"`.
|
|
31
31
|
*/
|
|
32
|
-
model?:
|
|
32
|
+
model?: string;
|
|
33
33
|
/**
|
|
34
34
|
* Spoken language. Uses ISO 639-3 (three-letter codes).
|
|
35
35
|
* Defaults to `"eng"` (English).
|
package/sdk/providers.ts
CHANGED
|
@@ -48,6 +48,9 @@ export type LlmProvider = ProviderDescriptor<string, Record<string, unknown>>;
|
|
|
48
48
|
/** Descriptor for a TTS provider. Returned by factories like `cartesia(...)`. */
|
|
49
49
|
export type TtsProvider = ProviderDescriptor<string, Record<string, unknown>>;
|
|
50
50
|
|
|
51
|
+
/** Descriptor for an S2S provider. Returned by factories like `openaiRealtime(...)`. */
|
|
52
|
+
export type S2sProvider = ProviderDescriptor<string, Record<string, unknown>>;
|
|
53
|
+
|
|
51
54
|
/** Descriptor for a KV backend. Returned by factories like `redisKv()`. */
|
|
52
55
|
export type KvProvider = ProviderDescriptor<string, Record<string, unknown>>;
|
|
53
56
|
|
|
@@ -67,14 +70,30 @@ export type SessionMode = "s2s" | "pipeline";
|
|
|
67
70
|
* Enforce the all-or-nothing provider rule and return the derived mode.
|
|
68
71
|
*
|
|
69
72
|
* Pipeline mode requires STT, LLM, and TTS all set; S2S mode requires
|
|
70
|
-
* none of them. Anything in-between is a configuration error.
|
|
73
|
+
* none of them. Anything in-between is a configuration error. An optional
|
|
74
|
+
* `s2s` descriptor selects a non-default S2S provider — it must not be
|
|
75
|
+
* combined with any pipeline field.
|
|
71
76
|
*/
|
|
72
|
-
export function assertProviderTriple(
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
export function assertProviderTriple(
|
|
78
|
+
stt: unknown,
|
|
79
|
+
llm: unknown,
|
|
80
|
+
tts: unknown,
|
|
81
|
+
s2s?: unknown,
|
|
82
|
+
): SessionMode {
|
|
83
|
+
const hasStt = stt != null;
|
|
84
|
+
const hasLlm = llm != null;
|
|
85
|
+
const hasTts = tts != null;
|
|
86
|
+
const hasS2s = s2s != null;
|
|
87
|
+
const anyPipeline = hasStt || hasLlm || hasTts;
|
|
88
|
+
const allSet = hasStt && hasLlm && hasTts;
|
|
89
|
+
const noneSetPipeline = !anyPipeline;
|
|
90
|
+
if (hasS2s && anyPipeline) {
|
|
91
|
+
throw new Error("s2s and the stt/llm/tts pipeline cannot be set together");
|
|
92
|
+
}
|
|
93
|
+
if (!(allSet || noneSetPipeline)) {
|
|
75
94
|
throw new Error("stt, llm, and tts must be set together");
|
|
76
95
|
}
|
|
77
|
-
return
|
|
96
|
+
return allSet ? "pipeline" : "s2s";
|
|
78
97
|
}
|
|
79
98
|
|
|
80
99
|
// -------- STT openable (host-only) ------------------------------------------
|
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
-
/**
|
|
3
|
-
* Schema–type alignment tests.
|
|
4
|
-
*
|
|
5
|
-
* These tests verify that Zod schemas and their derived TypeScript types
|
|
6
|
-
* stay in sync, and that schemas correctly reject invalid data. When a
|
|
7
|
-
* new field is added to a type but not its schema (or vice-versa), these
|
|
8
|
-
* tests break — preventing silent API drift.
|
|
9
|
-
*/
|
|
10
2
|
import { describe, expect, expectTypeOf, test } from "vitest";
|
|
11
3
|
import type { z } from "zod";
|
|
12
4
|
import { type AgentConfig, AgentConfigSchema, ToolSchemaSchema } from "./_internal-types.ts";
|
|
13
5
|
import { type ReadyConfig, ReadyConfigSchema } from "./protocol.ts";
|
|
14
6
|
import { type BuiltinTool, BuiltinToolSchema, type ToolChoice, ToolChoiceSchema } from "./types.ts";
|
|
15
7
|
|
|
16
|
-
// ── AgentConfigSchema ────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
8
|
describe("AgentConfigSchema", () => {
|
|
19
9
|
const valid: AgentConfig = {
|
|
20
10
|
name: "test-agent",
|
|
@@ -37,22 +27,13 @@ describe("AgentConfigSchema", () => {
|
|
|
37
27
|
expect(AgentConfigSchema.parse(full)).toEqual(full);
|
|
38
28
|
});
|
|
39
29
|
|
|
40
|
-
test(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
test("rejects negative maxSteps", () => {
|
|
49
|
-
expect(AgentConfigSchema.safeParse({ ...valid, maxSteps: -1 }).success).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("rejects invalid builtinTools", () => {
|
|
53
|
-
expect(AgentConfigSchema.safeParse({ ...valid, builtinTools: ["not_a_tool"] }).success).toBe(
|
|
54
|
-
false,
|
|
55
|
-
);
|
|
30
|
+
test.each([
|
|
31
|
+
["empty name", { name: "" }],
|
|
32
|
+
["non-integer maxSteps", { maxSteps: 2.5 }],
|
|
33
|
+
["negative maxSteps", { maxSteps: -1 }],
|
|
34
|
+
["invalid builtinTools", { builtinTools: ["not_a_tool"] }],
|
|
35
|
+
])("rejects %s", (_label, override) => {
|
|
36
|
+
expect(AgentConfigSchema.safeParse({ ...valid, ...override }).success).toBe(false);
|
|
56
37
|
});
|
|
57
38
|
|
|
58
39
|
test("type derived from schema matches AgentConfig", () => {
|
|
@@ -60,9 +41,9 @@ describe("AgentConfigSchema", () => {
|
|
|
60
41
|
});
|
|
61
42
|
});
|
|
62
43
|
|
|
63
|
-
// ── ToolSchemaSchema ─────────────────────────────────────────────────────
|
|
64
|
-
|
|
65
44
|
describe("ToolSchemaSchema", () => {
|
|
45
|
+
const base = { type: "function" as const, name: "n", description: "d", parameters: {} };
|
|
46
|
+
|
|
66
47
|
test("accepts valid tool schema", () => {
|
|
67
48
|
const valid = {
|
|
68
49
|
type: "function" as const,
|
|
@@ -73,47 +54,28 @@ describe("ToolSchemaSchema", () => {
|
|
|
73
54
|
expect(ToolSchemaSchema.parse(valid)).toEqual(valid);
|
|
74
55
|
});
|
|
75
56
|
|
|
76
|
-
test(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
description: "d",
|
|
82
|
-
parameters: {},
|
|
83
|
-
}).success,
|
|
84
|
-
).toBe(false);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("rejects empty description", () => {
|
|
88
|
-
expect(
|
|
89
|
-
ToolSchemaSchema.safeParse({
|
|
90
|
-
type: "function",
|
|
91
|
-
name: "n",
|
|
92
|
-
description: "",
|
|
93
|
-
parameters: {},
|
|
94
|
-
}).success,
|
|
95
|
-
).toBe(false);
|
|
57
|
+
test.each([
|
|
58
|
+
["empty name", { name: "" }],
|
|
59
|
+
["empty description", { description: "" }],
|
|
60
|
+
])("rejects %s", (_label, override) => {
|
|
61
|
+
expect(ToolSchemaSchema.safeParse({ ...base, ...override }).success).toBe(false);
|
|
96
62
|
});
|
|
97
63
|
|
|
98
64
|
test("ToolSchema is assignable from schema inference", () => {
|
|
99
|
-
// ToolSchema uses JSONSchema7 for parameters
|
|
100
|
-
//
|
|
101
|
-
// a parsed result should be assignable to ToolSchema (narrow → wide).
|
|
65
|
+
// ToolSchema uses JSONSchema7 for parameters (narrower than the runtime
|
|
66
|
+
// Record<string, unknown>); verify a parsed result satisfies the wider shape.
|
|
102
67
|
const parsed = ToolSchemaSchema.parse({
|
|
103
68
|
type: "function",
|
|
104
69
|
name: "test",
|
|
105
70
|
description: "test",
|
|
106
71
|
parameters: { type: "object" },
|
|
107
72
|
});
|
|
108
|
-
// Runtime check: shape matches
|
|
109
73
|
expect(parsed).toHaveProperty("name");
|
|
110
74
|
expect(parsed).toHaveProperty("description");
|
|
111
75
|
expect(parsed).toHaveProperty("parameters");
|
|
112
76
|
});
|
|
113
77
|
});
|
|
114
78
|
|
|
115
|
-
// ── ReadyConfigSchema ────────────────────────────────────────────────────
|
|
116
|
-
|
|
117
79
|
describe("ReadyConfigSchema", () => {
|
|
118
80
|
const valid: ReadyConfig = {
|
|
119
81
|
audioFormat: "pcm16",
|
|
@@ -125,12 +87,11 @@ describe("ReadyConfigSchema", () => {
|
|
|
125
87
|
expect(ReadyConfigSchema.parse(valid)).toEqual(valid);
|
|
126
88
|
});
|
|
127
89
|
|
|
128
|
-
test(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
expect(ReadyConfigSchema.safeParse({ ...valid, sampleRate: 0 }).success).toBe(false);
|
|
90
|
+
test.each([
|
|
91
|
+
["unknown audio format", { audioFormat: "mp3" }],
|
|
92
|
+
["non-positive sampleRate", { sampleRate: 0 }],
|
|
93
|
+
])("rejects %s", (_label, override) => {
|
|
94
|
+
expect(ReadyConfigSchema.safeParse({ ...valid, ...override }).success).toBe(false);
|
|
134
95
|
});
|
|
135
96
|
|
|
136
97
|
test("type derived from schema matches ReadyConfig", () => {
|
|
@@ -138,13 +99,8 @@ describe("ReadyConfigSchema", () => {
|
|
|
138
99
|
});
|
|
139
100
|
});
|
|
140
101
|
|
|
141
|
-
// ── BuiltinTool / ToolChoice drift guards ────────────────────────────────
|
|
142
|
-
|
|
143
102
|
describe("type ↔ schema alignment", () => {
|
|
144
103
|
test("BuiltinToolSchema values match BuiltinTool union", () => {
|
|
145
|
-
// Schema values are the source of truth; if the type adds a member
|
|
146
|
-
// without updating the schema (or vice versa), TypeScript will error
|
|
147
|
-
// on the drift guards in types.ts. This test documents the enum values.
|
|
148
104
|
expect(BuiltinToolSchema.options).toMatchInlineSnapshot(`
|
|
149
105
|
[
|
|
150
106
|
"web_search",
|
|
@@ -163,15 +119,11 @@ describe("type ↔ schema alignment", () => {
|
|
|
163
119
|
expectTypeOf<z.infer<typeof ToolChoiceSchema>>().toEqualTypeOf<ToolChoice>();
|
|
164
120
|
});
|
|
165
121
|
|
|
166
|
-
test("ToolChoiceSchema accepts
|
|
167
|
-
|
|
168
|
-
for (const v of variants) {
|
|
169
|
-
expect(ToolChoiceSchema.safeParse(v).success).toBe(true);
|
|
170
|
-
}
|
|
122
|
+
test.each<ToolChoice>(["auto", "required"])("ToolChoiceSchema accepts %s", (v) => {
|
|
123
|
+
expect(ToolChoiceSchema.safeParse(v).success).toBe(true);
|
|
171
124
|
});
|
|
172
125
|
|
|
173
|
-
test("ToolChoiceSchema rejects
|
|
174
|
-
expect(ToolChoiceSchema.safeParse(
|
|
175
|
-
expect(ToolChoiceSchema.safeParse("none").success).toBe(false);
|
|
126
|
+
test.each(["invalid", "none"])("ToolChoiceSchema rejects %s", (v) => {
|
|
127
|
+
expect(ToolChoiceSchema.safeParse(v).success).toBe(false);
|
|
176
128
|
});
|
|
177
129
|
});
|
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
-
/**
|
|
3
|
-
* Schema shape snapshot tests for public Zod schemas.
|
|
4
|
-
*
|
|
5
|
-
* These tests snapshot the field names of public schemas exported from
|
|
6
|
-
* `@alexkroman1/aai/protocol` and `@alexkroman1/aai/manifest`. A breaking
|
|
7
|
-
* snapshot signals a wire-format change (field additions, removals, or renames)
|
|
8
|
-
* that may require a changeset and protocol-version bump.
|
|
9
|
-
*
|
|
10
|
-
* For discriminated unions (ClientEventSchema, ServerMessageSchema,
|
|
11
|
-
* ClientMessageSchema), each option's shape keys are keyed by its
|
|
12
|
-
* discriminator value. For object schemas (AgentConfigSchema,
|
|
13
|
-
* ToolSchemaSchema), the top-level shape keys are snapshotted directly.
|
|
14
|
-
*/
|
|
15
2
|
import { describe, expect, test } from "vitest";
|
|
16
3
|
import { AgentConfigSchema, ToolSchemaSchema } from "./_internal-types.ts";
|
|
17
4
|
import {
|
|
@@ -24,22 +11,12 @@ import {
|
|
|
24
11
|
ServerMessageSchema,
|
|
25
12
|
} from "./protocol.ts";
|
|
26
13
|
|
|
27
|
-
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
14
|
type ZodObjectLike = { shape: Record<string, unknown> };
|
|
30
15
|
|
|
31
|
-
/**
|
|
32
|
-
* Extract shape keys (field names) from a ZodObject-like schema.
|
|
33
|
-
*/
|
|
34
16
|
function shapeKeys(schema: ZodObjectLike): string[] {
|
|
35
17
|
return Object.keys(schema.shape).sort();
|
|
36
18
|
}
|
|
37
19
|
|
|
38
|
-
/**
|
|
39
|
-
* Extract a map of { discriminatorValue → sorted shape keys } from a
|
|
40
|
-
* ZodDiscriminatedUnion. The discriminator literal may be a single value
|
|
41
|
-
* or an array (Zod v4 stores it as `.values`).
|
|
42
|
-
*/
|
|
43
20
|
function discriminatedUnionShapes(schema: {
|
|
44
21
|
options: Array<{ shape: Record<string, unknown> }>;
|
|
45
22
|
}): Record<string, string[]> {
|
|
@@ -48,9 +25,8 @@ function discriminatedUnionShapes(schema: {
|
|
|
48
25
|
const typeSchema = option.shape?.type as
|
|
49
26
|
| { _def?: { value?: string; values?: string[] } }
|
|
50
27
|
| undefined;
|
|
51
|
-
const def = typeSchema?._def;
|
|
52
28
|
// Zod v4 uses `.values` (array); Zod v3 uses `.value` (scalar)
|
|
53
|
-
const raw =
|
|
29
|
+
const raw = typeSchema?._def?.values ?? typeSchema?._def?.value;
|
|
54
30
|
const discriminatorValue = Array.isArray(raw) ? raw[0] : raw;
|
|
55
31
|
const key = String(discriminatorValue ?? "unknown");
|
|
56
32
|
result[key] = Object.keys(option.shape).sort();
|
|
@@ -58,8 +34,6 @@ function discriminatedUnionShapes(schema: {
|
|
|
58
34
|
return result;
|
|
59
35
|
}
|
|
60
36
|
|
|
61
|
-
// ── Protocol schemas ─────────────────────────────────────────────────────
|
|
62
|
-
|
|
63
37
|
describe("protocol schema shapes", () => {
|
|
64
38
|
test("ClientEventSchema option shapes", () => {
|
|
65
39
|
expect(discriminatedUnionShapes(ClientEventSchema)).toMatchSnapshot();
|
|
@@ -90,8 +64,6 @@ describe("protocol schema shapes", () => {
|
|
|
90
64
|
});
|
|
91
65
|
});
|
|
92
66
|
|
|
93
|
-
// ── Manifest schemas ──────────────────────────────────────────────────────
|
|
94
|
-
|
|
95
67
|
describe("manifest schema shapes", () => {
|
|
96
68
|
test("AgentConfigSchema shape", () => {
|
|
97
69
|
expect(shapeKeys(AgentConfigSchema)).toMatchSnapshot();
|
package/sdk/system-prompt.ts
CHANGED
|
@@ -6,14 +6,17 @@
|
|
|
6
6
|
import type { AgentConfig } from "./_internal-types.ts";
|
|
7
7
|
import { DEFAULT_SYSTEM_PROMPT } from "./types.ts";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
const DATE_FORMAT_OPTIONS: Intl.DateTimeFormatOptions = {
|
|
10
|
+
weekday: "long",
|
|
11
|
+
year: "numeric",
|
|
12
|
+
month: "long",
|
|
13
|
+
day: "numeric",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const TOOL_PREAMBLE =
|
|
17
|
+
"\n\nWhen you decide to use a tool, ALWAYS say a brief natural phrase BEFORE the tool call " +
|
|
18
|
+
'(e.g. "Let me look that up" or "One moment while I check"). ' +
|
|
19
|
+
"This fills silence while the tool executes. Keep preambles to one short sentence.";
|
|
17
20
|
|
|
18
21
|
const VOICE_RULES =
|
|
19
22
|
"\n\nCRITICAL OUTPUT RULES — you MUST follow these for EVERY response:\n" +
|
|
@@ -44,24 +47,19 @@ export function buildSystemPrompt(
|
|
|
44
47
|
config: AgentConfig,
|
|
45
48
|
opts: { hasTools: boolean; voice?: boolean; toolGuidance?: readonly string[] | undefined },
|
|
46
49
|
): string {
|
|
47
|
-
const
|
|
48
|
-
const agentInstructions =
|
|
49
|
-
|
|
50
|
-
? `\n\nAgent-Specific Instructions:\n${config.systemPrompt}`
|
|
51
|
-
: "";
|
|
52
|
-
|
|
53
|
-
const toolPreamble = hasTools
|
|
54
|
-
? "\n\nWhen you decide to use a tool, ALWAYS say a brief natural phrase BEFORE the tool call " +
|
|
55
|
-
'(e.g. "Let me look that up" or "One moment while I check"). ' +
|
|
56
|
-
"This fills silence while the tool executes. Keep preambles to one short sentence."
|
|
50
|
+
const hasCustomPrompt = config.systemPrompt && config.systemPrompt !== DEFAULT_SYSTEM_PROMPT;
|
|
51
|
+
const agentInstructions = hasCustomPrompt
|
|
52
|
+
? `\n\nAgent-Specific Instructions:\n${config.systemPrompt}`
|
|
57
53
|
: "";
|
|
58
54
|
|
|
55
|
+
const toolPreamble = opts.hasTools ? TOOL_PREAMBLE : "";
|
|
56
|
+
|
|
59
57
|
const guidance =
|
|
60
58
|
opts.toolGuidance && opts.toolGuidance.length > 0
|
|
61
59
|
? `\n\nBuilt-in Tool Usage:\n${opts.toolGuidance.join("\n")}`
|
|
62
60
|
: "";
|
|
63
61
|
|
|
64
|
-
const today =
|
|
62
|
+
const today = new Date().toLocaleDateString("en-US", DATE_FORMAT_OPTIONS);
|
|
65
63
|
|
|
66
64
|
return (
|
|
67
65
|
DEFAULT_SYSTEM_PROMPT +
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
-
/**
|
|
3
|
-
* Type-level tests for AgentDef and ToolDef type inference.
|
|
4
|
-
*
|
|
5
|
-
* These use vitest's expectTypeOf to verify that TypeScript correctly
|
|
6
|
-
* infers parameter types, state types, and context types without
|
|
7
|
-
* any runtime assertions. A failing type test means a type refactor
|
|
8
|
-
* broke inference for consumers.
|
|
9
|
-
*/
|
|
10
2
|
import { describe, expectTypeOf, test } from "vitest";
|
|
11
3
|
import { z } from "zod";
|
|
12
4
|
import type { AgentDef, Message, ToolContext, ToolDef } from "./types.ts";
|
|
13
5
|
|
|
6
|
+
const baseAgent = {
|
|
7
|
+
systemPrompt: "Be helpful.",
|
|
8
|
+
greeting: "Hello!",
|
|
9
|
+
maxSteps: 5,
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
14
12
|
describe("ToolDef type inference", () => {
|
|
15
13
|
test("infers parameter types in execute args", () => {
|
|
16
14
|
const _t: ToolDef<z.ZodObject<{ name: z.ZodString; count: z.ZodNumber }>> = {
|
|
@@ -19,7 +17,6 @@ describe("ToolDef type inference", () => {
|
|
|
19
17
|
execute: (args) => args,
|
|
20
18
|
};
|
|
21
19
|
|
|
22
|
-
// The execute function should receive typed args
|
|
23
20
|
type Args = Parameters<typeof _t.execute>[0];
|
|
24
21
|
expectTypeOf<Args>().toEqualTypeOf<{ name: string; count: number }>();
|
|
25
22
|
});
|
|
@@ -31,7 +28,6 @@ describe("ToolDef type inference", () => {
|
|
|
31
28
|
};
|
|
32
29
|
|
|
33
30
|
type Args = Parameters<typeof _t.execute>[0];
|
|
34
|
-
// Without parameters, args is inferred from the base ZodObject
|
|
35
31
|
expectTypeOf<Args>().toBeObject();
|
|
36
32
|
});
|
|
37
33
|
|
|
@@ -57,26 +53,16 @@ describe("ToolDef type inference", () => {
|
|
|
57
53
|
|
|
58
54
|
describe("AgentDef type inference", () => {
|
|
59
55
|
test("satisfies AgentDef type", () => {
|
|
60
|
-
const agent: AgentDef = {
|
|
61
|
-
name: "test",
|
|
62
|
-
systemPrompt: "Be helpful.",
|
|
63
|
-
greeting: "Hello!",
|
|
64
|
-
maxSteps: 5,
|
|
65
|
-
tools: {},
|
|
66
|
-
};
|
|
56
|
+
const agent: AgentDef = { ...baseAgent, name: "test", tools: {} };
|
|
67
57
|
expectTypeOf(agent).toMatchTypeOf<AgentDef>();
|
|
68
58
|
});
|
|
69
59
|
|
|
70
60
|
test("typed state flows through to tools", () => {
|
|
71
61
|
type MyState = { counter: number; name: string };
|
|
72
62
|
|
|
73
|
-
// This should compile without errors — state type flows
|
|
74
|
-
// through to tool execute context
|
|
75
63
|
const _agent: AgentDef<MyState> = {
|
|
64
|
+
...baseAgent,
|
|
76
65
|
name: "typed-state",
|
|
77
|
-
systemPrompt: "Be helpful.",
|
|
78
|
-
greeting: "Hello!",
|
|
79
|
-
maxSteps: 5,
|
|
80
66
|
state: () => ({ counter: 0, name: "test" }),
|
|
81
67
|
tools: {
|
|
82
68
|
inc: {
|
|
@@ -96,25 +82,13 @@ describe("AgentDef type inference", () => {
|
|
|
96
82
|
execute: ({ name }: { name: string }) => `Hello, ${name}!`,
|
|
97
83
|
};
|
|
98
84
|
|
|
99
|
-
const agent: AgentDef = {
|
|
100
|
-
name: "with-tool",
|
|
101
|
-
systemPrompt: "Be helpful.",
|
|
102
|
-
greeting: "Hello!",
|
|
103
|
-
maxSteps: 5,
|
|
104
|
-
tools: { greet },
|
|
105
|
-
};
|
|
85
|
+
const agent: AgentDef = { ...baseAgent, name: "with-tool", tools: { greet } };
|
|
106
86
|
|
|
107
87
|
expectTypeOf(agent.tools).toHaveProperty("greet");
|
|
108
88
|
});
|
|
109
89
|
|
|
110
90
|
test("required fields are present", () => {
|
|
111
|
-
const agent: AgentDef = {
|
|
112
|
-
name: "defaults",
|
|
113
|
-
systemPrompt: "Be helpful.",
|
|
114
|
-
greeting: "Hello!",
|
|
115
|
-
maxSteps: 5,
|
|
116
|
-
tools: {},
|
|
117
|
-
};
|
|
91
|
+
const agent: AgentDef = { ...baseAgent, name: "defaults", tools: {} };
|
|
118
92
|
expectTypeOf(agent.systemPrompt).toBeString();
|
|
119
93
|
expectTypeOf(agent.greeting).toBeString();
|
|
120
94
|
expectTypeOf(agent.tools).toBeObject();
|
package/sdk/types.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { Kv } from "./kv.ts";
|
|
|
8
8
|
import type {
|
|
9
9
|
KvProvider,
|
|
10
10
|
LlmProvider,
|
|
11
|
+
S2sProvider,
|
|
11
12
|
SttProvider,
|
|
12
13
|
TtsProvider,
|
|
13
14
|
VectorProvider,
|
|
@@ -242,6 +243,12 @@ export type AgentDef<S = Record<string, unknown>> = {
|
|
|
242
243
|
* pipeline mode.
|
|
243
244
|
*/
|
|
244
245
|
tts?: TtsProvider;
|
|
246
|
+
/**
|
|
247
|
+
* Pluggable S2S provider descriptor. When set, overrides the implicit
|
|
248
|
+
* AssemblyAI default. Mutually exclusive with the `stt`/`llm`/`tts`
|
|
249
|
+
* pipeline triple.
|
|
250
|
+
*/
|
|
251
|
+
s2s?: S2sProvider;
|
|
245
252
|
/** Pluggable KV backend. Falls back to platform default when omitted. */
|
|
246
253
|
kv?: KvProvider;
|
|
247
254
|
/** Pluggable Vector backend. Falls back to platform default when omitted. */
|
package/sdk/ws-upgrade.test.ts
CHANGED
|
@@ -5,44 +5,45 @@ import { parseWsUpgradeParams } from "./ws-upgrade.ts";
|
|
|
5
5
|
|
|
6
6
|
describe("parseWsUpgradeParams", () => {
|
|
7
7
|
test("returns defaults for URL with no query params", () => {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
expect(parseWsUpgradeParams("/websocket")).toEqual({
|
|
9
|
+
resumeFrom: undefined,
|
|
10
|
+
skipGreeting: false,
|
|
11
|
+
});
|
|
10
12
|
});
|
|
11
13
|
|
|
12
14
|
test("extracts sessionId and sets skipGreeting", () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
expect(parseWsUpgradeParams("/ws?sessionId=abc-123")).toEqual({
|
|
16
|
+
resumeFrom: "abc-123",
|
|
17
|
+
skipGreeting: true,
|
|
18
|
+
});
|
|
16
19
|
});
|
|
17
20
|
|
|
18
21
|
test("sets skipGreeting when resume param is present", () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
expect(parseWsUpgradeParams("/ws?resume=1")).toEqual({
|
|
23
|
+
resumeFrom: undefined,
|
|
24
|
+
skipGreeting: true,
|
|
25
|
+
});
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
test("sessionId takes precedence for resumeFrom", () => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
test("handles URL with no query string", () => {
|
|
31
|
-
const result = parseWsUpgradeParams("/websocket");
|
|
32
|
-
expect(result.resumeFrom).toBeUndefined();
|
|
33
|
-
expect(result.skipGreeting).toBe(false);
|
|
29
|
+
expect(parseWsUpgradeParams("/ws?resume=1&sessionId=sess-42")).toEqual({
|
|
30
|
+
resumeFrom: "sess-42",
|
|
31
|
+
skipGreeting: true,
|
|
32
|
+
});
|
|
34
33
|
});
|
|
35
34
|
|
|
36
35
|
test("handles full URL with query params", () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
expect(parseWsUpgradeParams("ws://localhost:3000/websocket?sessionId=s1")).toEqual({
|
|
37
|
+
resumeFrom: "s1",
|
|
38
|
+
skipGreeting: true,
|
|
39
|
+
});
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test("handles empty sessionId", () => {
|
|
43
|
-
const result = parseWsUpgradeParams("/ws?sessionId=");
|
|
44
43
|
// Empty string from URLSearchParams.get is truthy for ?? check
|
|
45
|
-
expect(
|
|
46
|
-
|
|
44
|
+
expect(parseWsUpgradeParams("/ws?sessionId=")).toEqual({
|
|
45
|
+
resumeFrom: "",
|
|
46
|
+
skipGreeting: true,
|
|
47
|
+
});
|
|
47
48
|
});
|
|
48
49
|
});
|
package/sdk/ws-upgrade.ts
CHANGED
|
@@ -5,9 +5,8 @@ export function parseWsUpgradeParams(rawUrl: string): {
|
|
|
5
5
|
resumeFrom?: string;
|
|
6
6
|
skipGreeting: boolean;
|
|
7
7
|
} {
|
|
8
|
-
const
|
|
9
|
-
const params = new URLSearchParams(search);
|
|
8
|
+
const params = new URLSearchParams(rawUrl.split("?")[1] ?? "");
|
|
10
9
|
const resumeFrom = params.get("sessionId") ?? undefined;
|
|
11
|
-
const skipGreeting = params.has("resume")
|
|
10
|
+
const skipGreeting = resumeFrom !== undefined || params.has("resume");
|
|
12
11
|
return resumeFrom !== undefined ? { resumeFrom, skipGreeting } : { skipGreeting };
|
|
13
12
|
}
|
package/tsdown.config.ts
CHANGED
|
@@ -2,17 +2,14 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { defineConfig } from "tsdown";
|
|
3
3
|
|
|
4
4
|
// Derive build entries from package.json exports so they can never drift.
|
|
5
|
-
const pkg = JSON.parse(readFileSync("package.json", "utf-8"))
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.map((v) => v["@dev/source"].replace(/^\.\//, "")),
|
|
14
|
-
),
|
|
15
|
-
];
|
|
5
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8")) as {
|
|
6
|
+
exports: Record<string, Record<string, string>>;
|
|
7
|
+
};
|
|
8
|
+
const sources = Object.values(pkg.exports)
|
|
9
|
+
.map((v) => v?.["@dev/source"])
|
|
10
|
+
.filter((s): s is string => typeof s === "string")
|
|
11
|
+
.map((s) => s.replace(/^\.\//, ""));
|
|
12
|
+
const entry = [...new Set(sources)];
|
|
16
13
|
|
|
17
14
|
export default defineConfig({
|
|
18
15
|
entry,
|