@alexkroman1/aai 0.12.3 → 1.0.3
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 +20 -0
- package/CHANGELOG.md +176 -0
- package/dist/constants-VTFoymJ-.js +47 -0
- package/dist/host/_run-code.d.ts +1 -1
- package/dist/host/_runtime-conformance.d.ts +4 -5
- package/dist/host/builtin-tools.d.ts +11 -9
- package/dist/host/runtime-barrel.d.ts +15 -0
- package/dist/{direct-executor-DRRrZUp0.js → host/runtime-barrel.js} +453 -348
- package/dist/host/runtime-config.d.ts +42 -0
- package/dist/host/runtime.d.ts +119 -35
- package/dist/host/s2s.d.ts +14 -38
- package/dist/host/server.d.ts +16 -8
- package/dist/host/session-ctx.d.ts +55 -0
- package/dist/host/session.d.ts +20 -70
- package/dist/host/tool-executor.d.ts +20 -0
- package/dist/host/unstorage-kv.d.ts +1 -1
- package/dist/host/ws-handler.d.ts +4 -2
- package/dist/index.d.ts +9 -20
- package/dist/index.js +63 -2
- package/dist/{isolate → sdk}/_internal-types.d.ts +5 -9
- package/dist/{isolate → sdk}/constants.d.ts +6 -4
- package/dist/sdk/define.d.ts +66 -0
- package/dist/{isolate → sdk}/kv.d.ts +1 -49
- package/dist/sdk/manifest-barrel.d.ts +8 -0
- package/dist/sdk/manifest-barrel.js +52 -0
- package/dist/sdk/manifest.d.ts +50 -0
- package/dist/{isolate → sdk}/protocol.d.ts +59 -36
- package/dist/sdk/protocol.js +163 -0
- package/dist/{isolate → sdk}/system-prompt.d.ts +2 -2
- package/dist/sdk/types.d.ts +201 -0
- package/dist/sdk/ws-upgrade.d.ts +5 -0
- package/dist/{system-prompt-DYAYFW99.js → system-prompt-nik_iavo.js} +10 -10
- package/dist/types-Cfx_4QDK.js +39 -0
- package/dist/ws-upgrade-BeOQ7fXL.js +30 -0
- package/exports-no-dev-deps.test.ts +62 -0
- package/host/_mock-ws.ts +185 -0
- package/host/_run-code.ts +217 -0
- package/host/_runtime-conformance.ts +143 -0
- package/host/_test-utils.ts +276 -0
- package/host/builtin-tools.test.ts +774 -0
- package/host/builtin-tools.ts +255 -0
- package/host/cleanup.test.ts +422 -0
- package/host/fixture-replay.test.ts +463 -0
- package/host/fixtures/README.md +40 -0
- package/host/fixtures/greeting-session-sequence.json +40 -0
- package/host/fixtures/reply-audio-samples.json +42 -0
- package/host/fixtures/reply-lifecycle.json +21 -0
- package/host/fixtures/session-ready.json +48 -0
- package/host/fixtures/session-updated.json +45 -0
- package/host/fixtures/simple-question-sequence.json +73 -0
- package/host/fixtures/tool-call-sequence.json +114 -0
- package/host/fixtures/tool-calls.json +11 -0
- package/host/fixtures/tool-config-session-sequence.json +51 -0
- package/host/fixtures/user-speech-recognition.json +30 -0
- package/host/fixtures/web-search-sequence.json +122 -0
- package/host/integration.test.ts +222 -0
- package/host/runtime-barrel.ts +25 -0
- package/host/runtime-config.test.ts +71 -0
- package/host/runtime-config.ts +99 -0
- package/host/runtime.test.ts +641 -0
- package/host/runtime.ts +308 -0
- package/host/s2s-fixtures.test.ts +237 -0
- package/host/s2s.test.ts +562 -0
- package/host/s2s.ts +310 -0
- package/host/server-shutdown.test.ts +76 -0
- package/host/server.test.ts +116 -0
- package/host/server.ts +223 -0
- package/host/session-ctx.ts +107 -0
- package/host/session-fixture-replay.test.ts +136 -0
- package/host/session-prompt.test.ts +77 -0
- package/host/session.test.ts +590 -0
- package/host/session.ts +370 -0
- package/host/tool-executor.test.ts +124 -0
- package/host/tool-executor.ts +80 -0
- package/host/unstorage-kv.test.ts +99 -0
- package/host/unstorage-kv.ts +69 -0
- package/host/ws-handler.test.ts +739 -0
- package/host/ws-handler.ts +255 -0
- package/index.ts +16 -0
- package/package.json +24 -72
- package/sdk/_internal-types.test.ts +34 -0
- package/sdk/_internal-types.ts +115 -0
- package/sdk/compat-fixtures/README.md +26 -0
- package/sdk/compat-fixtures/v1.json +68 -0
- package/sdk/constants.ts +77 -0
- package/sdk/define.test.ts +57 -0
- package/sdk/define.ts +88 -0
- package/sdk/kv.ts +60 -0
- package/sdk/manifest-barrel.ts +12 -0
- package/sdk/manifest.test.ts +56 -0
- package/sdk/manifest.ts +89 -0
- package/sdk/protocol-compat.test.ts +187 -0
- package/sdk/protocol-snapshot.test.ts +199 -0
- package/sdk/protocol.test.ts +170 -0
- package/sdk/protocol.ts +223 -0
- package/sdk/schema-alignment.test.ts +191 -0
- package/sdk/system-prompt.test.ts +111 -0
- package/sdk/system-prompt.ts +74 -0
- package/sdk/tsconfig.json +12 -0
- package/sdk/types-inference.test.ts +122 -0
- package/sdk/types.test.ts +14 -0
- package/sdk/types.ts +226 -0
- package/sdk/utils.test.ts +52 -0
- package/sdk/utils.ts +20 -0
- package/sdk/ws-upgrade.test.ts +48 -0
- package/sdk/ws-upgrade.ts +13 -0
- package/tsconfig.build.json +14 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +26 -0
- package/vitest.config.ts +17 -0
- package/dist/host/_test-utils.d.ts +0 -73
- package/dist/host/direct-executor.d.ts +0 -130
- package/dist/host/index.d.ts +0 -19
- package/dist/host/index.js +0 -165
- package/dist/host/matchers.d.ts +0 -20
- package/dist/host/matchers.js +0 -41
- package/dist/host/server.js +0 -164
- package/dist/host/testing.d.ts +0 -294
- package/dist/host/testing.js +0 -2
- package/dist/host/vite-plugin.d.ts +0 -15
- package/dist/host/vite-plugin.js +0 -83
- package/dist/isolate/_kv-utils.d.ts +0 -10
- package/dist/isolate/_utils.js +0 -17
- package/dist/isolate/hooks.d.ts +0 -44
- package/dist/isolate/hooks.js +0 -58
- package/dist/isolate/index.d.ts +0 -18
- package/dist/isolate/index.js +0 -6
- package/dist/isolate/kv.js +0 -1
- package/dist/isolate/protocol.js +0 -2
- package/dist/isolate/types.d.ts +0 -418
- package/dist/isolate/types.js +0 -175
- package/dist/protocol-rcOrz7T3.js +0 -183
- package/dist/testing-BreLdpq-.js +0 -513
- package/dist/types.test-d.d.ts +0 -7
- /package/dist/{isolate/_utils.d.ts → sdk/utils.d.ts} +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
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
|
+
import { describe, expect, expectTypeOf, test } from "vitest";
|
|
11
|
+
import type { z } from "zod";
|
|
12
|
+
import { type AgentConfig, AgentConfigSchema, ToolSchemaSchema } from "./_internal-types.ts";
|
|
13
|
+
import {
|
|
14
|
+
type ReadyConfig,
|
|
15
|
+
ReadyConfigSchema,
|
|
16
|
+
type TurnConfig,
|
|
17
|
+
TurnConfigSchema,
|
|
18
|
+
} from "./protocol.ts";
|
|
19
|
+
import { type BuiltinTool, BuiltinToolSchema, type ToolChoice, ToolChoiceSchema } from "./types.ts";
|
|
20
|
+
|
|
21
|
+
// ── AgentConfigSchema ────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
describe("AgentConfigSchema", () => {
|
|
24
|
+
const valid: AgentConfig = {
|
|
25
|
+
name: "test-agent",
|
|
26
|
+
systemPrompt: "Be helpful",
|
|
27
|
+
greeting: "Hello",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
test("accepts valid minimal config", () => {
|
|
31
|
+
expect(AgentConfigSchema.parse(valid)).toEqual(valid);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("accepts full config with all optional fields", () => {
|
|
35
|
+
const full: AgentConfig = {
|
|
36
|
+
...valid,
|
|
37
|
+
sttPrompt: "Transcribe accurately",
|
|
38
|
+
maxSteps: 10,
|
|
39
|
+
toolChoice: "auto",
|
|
40
|
+
builtinTools: ["web_search", "run_code"],
|
|
41
|
+
};
|
|
42
|
+
expect(AgentConfigSchema.parse(full)).toEqual(full);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("rejects empty name", () => {
|
|
46
|
+
expect(AgentConfigSchema.safeParse({ ...valid, name: "" }).success).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("rejects non-integer maxSteps", () => {
|
|
50
|
+
expect(AgentConfigSchema.safeParse({ ...valid, maxSteps: 2.5 }).success).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("rejects negative maxSteps", () => {
|
|
54
|
+
expect(AgentConfigSchema.safeParse({ ...valid, maxSteps: -1 }).success).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("rejects invalid builtinTools", () => {
|
|
58
|
+
expect(AgentConfigSchema.safeParse({ ...valid, builtinTools: ["not_a_tool"] }).success).toBe(
|
|
59
|
+
false,
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("type derived from schema matches AgentConfig", () => {
|
|
64
|
+
expectTypeOf<z.infer<typeof AgentConfigSchema>>().toEqualTypeOf<AgentConfig>();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── ToolSchemaSchema ─────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
describe("ToolSchemaSchema", () => {
|
|
71
|
+
test("accepts valid tool schema", () => {
|
|
72
|
+
const valid = {
|
|
73
|
+
name: "get_weather",
|
|
74
|
+
description: "Get weather",
|
|
75
|
+
parameters: { type: "object", properties: { city: { type: "string" } } },
|
|
76
|
+
};
|
|
77
|
+
expect(ToolSchemaSchema.parse(valid)).toEqual(valid);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("rejects empty name", () => {
|
|
81
|
+
expect(ToolSchemaSchema.safeParse({ name: "", description: "d", parameters: {} }).success).toBe(
|
|
82
|
+
false,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("rejects empty description", () => {
|
|
87
|
+
expect(ToolSchemaSchema.safeParse({ name: "n", description: "", parameters: {} }).success).toBe(
|
|
88
|
+
false,
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("ToolSchema is assignable from schema inference", () => {
|
|
93
|
+
// ToolSchema uses JSONSchema7 for parameters which is more specific than
|
|
94
|
+
// the runtime schema's Record<string, unknown>. Verify the direction:
|
|
95
|
+
// a parsed result should be assignable to ToolSchema (narrow → wide).
|
|
96
|
+
const parsed = ToolSchemaSchema.parse({
|
|
97
|
+
name: "test",
|
|
98
|
+
description: "test",
|
|
99
|
+
parameters: { type: "object" },
|
|
100
|
+
});
|
|
101
|
+
// Runtime check: shape matches
|
|
102
|
+
expect(parsed).toHaveProperty("name");
|
|
103
|
+
expect(parsed).toHaveProperty("description");
|
|
104
|
+
expect(parsed).toHaveProperty("parameters");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ── TurnConfigSchema ─────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
describe("TurnConfigSchema", () => {
|
|
111
|
+
test("accepts empty config", () => {
|
|
112
|
+
expect(TurnConfigSchema.parse({})).toEqual({});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("accepts full config", () => {
|
|
116
|
+
const full: TurnConfig = { maxSteps: 3 };
|
|
117
|
+
expect(TurnConfigSchema.parse(full)).toEqual(full);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("rejects non-positive maxSteps", () => {
|
|
121
|
+
expect(TurnConfigSchema.safeParse({ maxSteps: 0 }).success).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("type derived from schema matches TurnConfig", () => {
|
|
125
|
+
expectTypeOf<z.infer<typeof TurnConfigSchema>>().toEqualTypeOf<TurnConfig>();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ── ReadyConfigSchema ────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
describe("ReadyConfigSchema", () => {
|
|
132
|
+
const valid: ReadyConfig = {
|
|
133
|
+
audioFormat: "pcm16",
|
|
134
|
+
sampleRate: 16_000,
|
|
135
|
+
ttsSampleRate: 24_000,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
test("accepts valid config", () => {
|
|
139
|
+
expect(ReadyConfigSchema.parse(valid)).toEqual(valid);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("rejects unknown audio format", () => {
|
|
143
|
+
expect(ReadyConfigSchema.safeParse({ ...valid, audioFormat: "mp3" }).success).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("rejects non-positive sampleRate", () => {
|
|
147
|
+
expect(ReadyConfigSchema.safeParse({ ...valid, sampleRate: 0 }).success).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("type derived from schema matches ReadyConfig", () => {
|
|
151
|
+
expectTypeOf<z.infer<typeof ReadyConfigSchema>>().toEqualTypeOf<ReadyConfig>();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ── BuiltinTool / ToolChoice drift guards ────────────────────────────────
|
|
156
|
+
|
|
157
|
+
describe("type ↔ schema alignment", () => {
|
|
158
|
+
test("BuiltinToolSchema values match BuiltinTool union", () => {
|
|
159
|
+
// Schema values are the source of truth; if the type adds a member
|
|
160
|
+
// without updating the schema (or vice versa), TypeScript will error
|
|
161
|
+
// on the drift guards in types.ts. This test documents the enum values.
|
|
162
|
+
expect(BuiltinToolSchema.options).toMatchInlineSnapshot(`
|
|
163
|
+
[
|
|
164
|
+
"web_search",
|
|
165
|
+
"visit_webpage",
|
|
166
|
+
"fetch_json",
|
|
167
|
+
"run_code",
|
|
168
|
+
]
|
|
169
|
+
`);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("BuiltinTool type equals schema inference", () => {
|
|
173
|
+
expectTypeOf<z.infer<typeof BuiltinToolSchema>>().toEqualTypeOf<BuiltinTool>();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("ToolChoice type equals schema inference", () => {
|
|
177
|
+
expectTypeOf<z.infer<typeof ToolChoiceSchema>>().toEqualTypeOf<ToolChoice>();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("ToolChoiceSchema accepts all ToolChoice variants", () => {
|
|
181
|
+
const variants: ToolChoice[] = ["auto", "required"];
|
|
182
|
+
for (const v of variants) {
|
|
183
|
+
expect(ToolChoiceSchema.safeParse(v).success).toBe(true);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("ToolChoiceSchema rejects invalid variants", () => {
|
|
188
|
+
expect(ToolChoiceSchema.safeParse("invalid").success).toBe(false);
|
|
189
|
+
expect(ToolChoiceSchema.safeParse("none").success).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
4
|
+
import { makeConfig } from "../host/_test-utils.ts";
|
|
5
|
+
import { buildSystemPrompt } from "./system-prompt.ts";
|
|
6
|
+
import { DEFAULT_SYSTEM_PROMPT } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
describe("buildSystemPrompt", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.useFakeTimers();
|
|
11
|
+
// Pin to Wednesday, January 15, 2025
|
|
12
|
+
vi.setSystemTime(new Date("2025-01-15T12:00:00Z"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.useRealTimers();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("starts with DEFAULT_SYSTEM_PROMPT when no custom instructions", () => {
|
|
20
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: false });
|
|
21
|
+
expect(result.startsWith(DEFAULT_SYSTEM_PROMPT)).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("does not include agent-specific instructions section for default instructions", () => {
|
|
25
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: false });
|
|
26
|
+
expect(result).not.toContain("Agent-Specific Instructions:");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("appends custom agent instructions", () => {
|
|
30
|
+
const custom = "You are a pirate. Always speak like one.";
|
|
31
|
+
const result = buildSystemPrompt(makeConfig({ systemPrompt: custom }), { hasTools: false });
|
|
32
|
+
expect(result).toContain("Agent-Specific Instructions:");
|
|
33
|
+
expect(result).toContain(custom);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("includes tool preamble when hasTools is true", () => {
|
|
37
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: true });
|
|
38
|
+
expect(result).toContain("ALWAYS say a brief natural phrase BEFORE the tool call");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("omits tool preamble when hasTools is false", () => {
|
|
42
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: false });
|
|
43
|
+
expect(result).not.toContain("ALWAYS say a brief natural phrase BEFORE the tool call");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("appends voice rules when voice is true", () => {
|
|
47
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: false, voice: true });
|
|
48
|
+
expect(result).toContain("CRITICAL OUTPUT RULES");
|
|
49
|
+
expect(result).toContain("NEVER use markdown");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("omits voice rules when voice is false", () => {
|
|
53
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: false, voice: false });
|
|
54
|
+
expect(result).not.toContain("CRITICAL OUTPUT RULES");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("omits voice rules when voice is undefined", () => {
|
|
58
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: false });
|
|
59
|
+
expect(result).not.toContain("CRITICAL OUTPUT RULES");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("includes correctly formatted date string", () => {
|
|
63
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: false });
|
|
64
|
+
expect(result).toContain("Today's date is Wednesday, January 15, 2025.");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("date format uses en-US locale with weekday, month, day, and year", () => {
|
|
68
|
+
// Advance to a different date to verify format consistency
|
|
69
|
+
vi.setSystemTime(new Date("2025-12-31T12:00:00Z"));
|
|
70
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: false });
|
|
71
|
+
expect(result).toContain("Today's date is Wednesday, December 31, 2025.");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("voice + hasTools includes both voice rules and tool preamble", () => {
|
|
75
|
+
const result = buildSystemPrompt(makeConfig(), { hasTools: true, voice: true });
|
|
76
|
+
expect(result).toContain("CRITICAL OUTPUT RULES");
|
|
77
|
+
expect(result).toContain("ALWAYS say a brief natural phrase BEFORE the tool call");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("custom instructions + voice + tools includes all sections", () => {
|
|
81
|
+
const result = buildSystemPrompt(makeConfig({ systemPrompt: "Be concise." }), {
|
|
82
|
+
hasTools: true,
|
|
83
|
+
voice: true,
|
|
84
|
+
});
|
|
85
|
+
expect(result).toContain("Agent-Specific Instructions:");
|
|
86
|
+
expect(result).toContain("Be concise.");
|
|
87
|
+
expect(result).toContain("CRITICAL OUTPUT RULES");
|
|
88
|
+
expect(result).toContain("ALWAYS say a brief natural phrase BEFORE the tool call");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("sections appear in correct order", () => {
|
|
92
|
+
const result = buildSystemPrompt(makeConfig({ systemPrompt: "Custom rules." }), {
|
|
93
|
+
hasTools: true,
|
|
94
|
+
voice: true,
|
|
95
|
+
});
|
|
96
|
+
const dateIdx = result.indexOf("Today's date is");
|
|
97
|
+
const instructionsIdx = result.indexOf("Agent-Specific Instructions:");
|
|
98
|
+
const toolIdx = result.indexOf("ALWAYS say a brief natural phrase");
|
|
99
|
+
const voiceIdx = result.indexOf("CRITICAL OUTPUT RULES");
|
|
100
|
+
|
|
101
|
+
expect(dateIdx).toBeGreaterThan(0);
|
|
102
|
+
expect(instructionsIdx).toBeGreaterThan(dateIdx);
|
|
103
|
+
expect(toolIdx).toBeGreaterThan(instructionsIdx);
|
|
104
|
+
expect(voiceIdx).toBeGreaterThan(toolIdx);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("empty custom instructions treated same as default", () => {
|
|
108
|
+
const result = buildSystemPrompt(makeConfig({ systemPrompt: "" }), { hasTools: false });
|
|
109
|
+
expect(result).not.toContain("Agent-Specific Instructions:");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
+
/**
|
|
3
|
+
* System prompt builder for S2S sessions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AgentConfig } from "./_internal-types.ts";
|
|
7
|
+
import { DEFAULT_SYSTEM_PROMPT } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
function getFormattedDate(): string {
|
|
10
|
+
return new Date().toLocaleDateString("en-US", {
|
|
11
|
+
weekday: "long",
|
|
12
|
+
year: "numeric",
|
|
13
|
+
month: "long",
|
|
14
|
+
day: "numeric",
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const VOICE_RULES =
|
|
19
|
+
"\n\nCRITICAL OUTPUT RULES — you MUST follow these for EVERY response:\n" +
|
|
20
|
+
"Your response will be spoken aloud by a TTS system and displayed as plain text.\n" +
|
|
21
|
+
"- NEVER use markdown: no **, no *, no _, no #, no `, no [](), no ---\n" +
|
|
22
|
+
"- NEVER use bullet points (-, *, •) or numbered lists (1., 2.)\n" +
|
|
23
|
+
"- NEVER use code blocks or inline code\n" +
|
|
24
|
+
"- NEVER mention tools, search, APIs, or technical failures to the user. " +
|
|
25
|
+
"If a tool returns no results, just answer naturally without explaining why.\n" +
|
|
26
|
+
"- Write exactly as you would say it out loud to a friend\n" +
|
|
27
|
+
'- Use short conversational sentences. To list things, say "First," "Next," "Finally,"\n' +
|
|
28
|
+
"- Keep responses concise — 1 to 3 sentences max";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build the system prompt sent to the LLM from the agent configuration.
|
|
32
|
+
*
|
|
33
|
+
* Assembles the default system prompt, today's date, agent-specific instructions,
|
|
34
|
+
* and optional sections for tool usage preamble and voice output rules.
|
|
35
|
+
*
|
|
36
|
+
* @param config - The serializable agent configuration (name, systemPrompt, etc.).
|
|
37
|
+
* @param opts.hasTools - When `true`, appends a preamble instructing the LLM to
|
|
38
|
+
* speak a brief phrase before each tool call to fill silence.
|
|
39
|
+
* @param opts.voice - When `true`, appends strict voice-specific output rules
|
|
40
|
+
* (no markdown, no bullet points, conversational tone, concise responses).
|
|
41
|
+
* @returns The assembled system prompt string.
|
|
42
|
+
*/
|
|
43
|
+
export function buildSystemPrompt(
|
|
44
|
+
config: AgentConfig,
|
|
45
|
+
opts: { hasTools: boolean; voice?: boolean; toolGuidance?: readonly string[] | undefined },
|
|
46
|
+
): string {
|
|
47
|
+
const { hasTools } = opts;
|
|
48
|
+
const agentInstructions =
|
|
49
|
+
config.systemPrompt && config.systemPrompt !== DEFAULT_SYSTEM_PROMPT
|
|
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."
|
|
57
|
+
: "";
|
|
58
|
+
|
|
59
|
+
const guidance =
|
|
60
|
+
opts.toolGuidance && opts.toolGuidance.length > 0
|
|
61
|
+
? `\n\nBuilt-in Tool Usage:\n${opts.toolGuidance.join("\n")}`
|
|
62
|
+
: "";
|
|
63
|
+
|
|
64
|
+
const today = getFormattedDate();
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
DEFAULT_SYSTEM_PROMPT +
|
|
68
|
+
`\n\nToday's date is ${today}.` +
|
|
69
|
+
agentInstructions +
|
|
70
|
+
toolPreamble +
|
|
71
|
+
guidance +
|
|
72
|
+
(opts.voice ? VOICE_RULES : "")
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"types": [],
|
|
5
|
+
"lib": ["ESNext", "DOM"],
|
|
6
|
+
"noEmit": true,
|
|
7
|
+
"incremental": true,
|
|
8
|
+
"tsBuildInfoFile": "../../../.tsbuildinfo/aai-sdk.tsbuildinfo"
|
|
9
|
+
},
|
|
10
|
+
"include": ["./**/*.ts"],
|
|
11
|
+
"exclude": ["**/*.test.ts", "**/*.test-d.ts"]
|
|
12
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
import { describe, expectTypeOf, test } from "vitest";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import type { AgentDef, Message, ToolContext, ToolDef } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
describe("ToolDef type inference", () => {
|
|
15
|
+
test("infers parameter types in execute args", () => {
|
|
16
|
+
const _t: ToolDef<z.ZodObject<{ name: z.ZodString; count: z.ZodNumber }>> = {
|
|
17
|
+
description: "test",
|
|
18
|
+
parameters: z.object({ name: z.string(), count: z.number() }),
|
|
19
|
+
execute: (args) => args,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// The execute function should receive typed args
|
|
23
|
+
type Args = Parameters<typeof _t.execute>[0];
|
|
24
|
+
expectTypeOf<Args>().toEqualTypeOf<{ name: string; count: number }>();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("ToolDef without parameters has unknown args", () => {
|
|
28
|
+
const _t: ToolDef = {
|
|
29
|
+
description: "test",
|
|
30
|
+
execute: (args) => args,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type Args = Parameters<typeof _t.execute>[0];
|
|
34
|
+
// Without parameters, args is inferred from the base ZodObject
|
|
35
|
+
expectTypeOf<Args>().toBeObject();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("execute receives ToolContext as second arg", () => {
|
|
39
|
+
const _t: ToolDef<z.ZodObject<{ x: z.ZodString }>> = {
|
|
40
|
+
description: "test",
|
|
41
|
+
parameters: z.object({ x: z.string() }),
|
|
42
|
+
execute: (_args, ctx) => ctx,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type Ctx = Parameters<typeof _t.execute>[1];
|
|
46
|
+
expectTypeOf<Ctx>().toMatchTypeOf<ToolContext>();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("ToolContext provides kv, env, messages", () => {
|
|
50
|
+
expectTypeOf<ToolContext>().toHaveProperty("kv");
|
|
51
|
+
expectTypeOf<ToolContext>().toHaveProperty("env");
|
|
52
|
+
expectTypeOf<ToolContext>().toHaveProperty("messages");
|
|
53
|
+
expectTypeOf<ToolContext["messages"]>().toEqualTypeOf<readonly Message[]>();
|
|
54
|
+
expectTypeOf<ToolContext["env"]>().toEqualTypeOf<Readonly<Record<string, string>>>();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("AgentDef type inference", () => {
|
|
59
|
+
test("satisfies AgentDef type", () => {
|
|
60
|
+
const agent: AgentDef = {
|
|
61
|
+
name: "test",
|
|
62
|
+
systemPrompt: "Be helpful.",
|
|
63
|
+
greeting: "Hello!",
|
|
64
|
+
maxSteps: 5,
|
|
65
|
+
tools: {},
|
|
66
|
+
};
|
|
67
|
+
expectTypeOf(agent).toMatchTypeOf<AgentDef>();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("typed state flows through to tools", () => {
|
|
71
|
+
type MyState = { counter: number; name: string };
|
|
72
|
+
|
|
73
|
+
// This should compile without errors — state type flows
|
|
74
|
+
// through to tool execute context
|
|
75
|
+
const _agent: AgentDef<MyState> = {
|
|
76
|
+
name: "typed-state",
|
|
77
|
+
systemPrompt: "Be helpful.",
|
|
78
|
+
greeting: "Hello!",
|
|
79
|
+
maxSteps: 5,
|
|
80
|
+
state: () => ({ counter: 0, name: "test" }),
|
|
81
|
+
tools: {
|
|
82
|
+
inc: {
|
|
83
|
+
description: "Increment",
|
|
84
|
+
execute: (_args, ctx) => {
|
|
85
|
+
expectTypeOf(ctx.state).toEqualTypeOf<MyState>();
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("tools field accepts ToolDef objects", () => {
|
|
93
|
+
const greet: ToolDef<z.ZodObject<{ name: z.ZodString }>> = {
|
|
94
|
+
description: "Greet",
|
|
95
|
+
parameters: z.object({ name: z.string() }),
|
|
96
|
+
execute: ({ name }: { name: string }) => `Hello, ${name}!`,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const agent: AgentDef = {
|
|
100
|
+
name: "with-tool",
|
|
101
|
+
systemPrompt: "Be helpful.",
|
|
102
|
+
greeting: "Hello!",
|
|
103
|
+
maxSteps: 5,
|
|
104
|
+
tools: { greet },
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
expectTypeOf(agent.tools).toHaveProperty("greet");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
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
|
+
};
|
|
118
|
+
expectTypeOf(agent.systemPrompt).toBeString();
|
|
119
|
+
expectTypeOf(agent.greeting).toBeString();
|
|
120
|
+
expectTypeOf(agent.tools).toBeObject();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { DEFAULT_GREETING, DEFAULT_SYSTEM_PROMPT } from "./types.ts";
|
|
3
|
+
|
|
4
|
+
describe("constants", () => {
|
|
5
|
+
test("DEFAULT_SYSTEM_PROMPT is a non-empty string", () => {
|
|
6
|
+
expect(typeof DEFAULT_SYSTEM_PROMPT).toBe("string");
|
|
7
|
+
expect(DEFAULT_SYSTEM_PROMPT.length).toBeGreaterThan(0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("DEFAULT_GREETING is a non-empty string", () => {
|
|
11
|
+
expect(typeof DEFAULT_GREETING).toBe("string");
|
|
12
|
+
expect(DEFAULT_GREETING.length).toBeGreaterThan(0);
|
|
13
|
+
});
|
|
14
|
+
});
|