Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,13 @@
"examples"
],
"scripts": {
"gen:types": "node scripts/gen-llm-vendors.mjs",
"prebuild": "npm run gen:types",
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest run",
"test:watch": "vitest",
"pretypecheck": "npm run gen:types",
"typecheck": "tsc --noEmit",
"lint": "eslint src/",
"docs": "typedoc",
Expand Down
62 changes: 62 additions & 0 deletions typescript/scripts/gen-llm-vendors.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env node
/**
* Code generator: derive the LLM vendor union from the source of truth.
*
* The agent verb's `llm.vendor` is an enum defined once, in
* `@jambonz/schema` (verbs/agent.schema.json). Hand-maintaining a matching
* TypeScript union here drifts every time a vendor is added to the schema.
* Instead we read the enum at build time and emit it as a TS literal union.
*
* Output: src/types/llm-vendors.generated.ts (committed; regenerated on prebuild)
* Run: npm run gen:types
*/
import { createRequire } from 'node:module';
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const require = createRequire(import.meta.url);
const __dirname = dirname(fileURLToPath(import.meta.url));

// Resolve @jambonz/schema's location via its package.json (no `exports` field,
// so subpath file reads are permitted), then read the agent verb schema.
const schemaPkgJson = require.resolve('@jambonz/schema/package.json');
const schemaRoot = dirname(schemaPkgJson);
const agentSchemaPath = join(schemaRoot, 'verbs', 'agent.schema.json');

const agentSchema = JSON.parse(readFileSync(agentSchemaPath, 'utf8'));
const enumValues = agentSchema?.properties?.llm?.properties?.vendor?.enum;

if (!Array.isArray(enumValues) || enumValues.length === 0) {
console.error(
`[gen-llm-vendors] Could not find llm.vendor.enum in ${agentSchemaPath}`
);
process.exit(1);
}

const schemaVersion = JSON.parse(readFileSync(schemaPkgJson, 'utf8')).version;
const members = enumValues.map((v) => ` '${v}',`).join('\n');

const out = `// AUTO-GENERATED — DO NOT EDIT BY HAND.
// Source of truth: @jambonz/schema@${schemaVersion} verbs/agent.schema.json (llm.vendor.enum)
// Regenerate with: npm run gen:types
//
// This file derives the LLM vendor list from the JSON schema so the SDK's
// types never drift from the schema when a new vendor is added.

/** Supported LLM vendors for the agent verb, derived from the schema enum. */
export const LLM_VENDORS = [
${members}
] as const;

/** Union of LLM vendor ids accepted by the agent verb's \`llm.vendor\`. */
export type LlmVendor = (typeof LLM_VENDORS)[number];
`;

const outPath = resolve(__dirname, '..', 'src', 'types', 'llm-vendors.generated.ts');
mkdirSync(dirname(outPath), { recursive: true });
writeFileSync(outPath, out);

console.log(
`[gen-llm-vendors] Wrote ${enumValues.length} vendors from @jambonz/schema@${schemaVersion} -> src/types/llm-vendors.generated.ts`
);
4 changes: 4 additions & 0 deletions typescript/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export type {
VerbName,
} from './verbs.js';

// LLM vendors (derived from @jambonz/schema)
export type { LlmVendor } from './llm-vendors.generated.js';
export { LLM_VENDORS } from './llm-vendors.generated.js';

// Session
export type {
AgentEvent,
Expand Down
24 changes: 24 additions & 0 deletions typescript/src/types/llm-vendors.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// AUTO-GENERATED — DO NOT EDIT BY HAND.
// Source of truth: @jambonz/schema@0.3.8 verbs/agent.schema.json (llm.vendor.enum)
// Regenerate with: npm run gen:types
//
// This file derives the LLM vendor list from the JSON schema so the SDK's
// types never drift from the schema when a new vendor is added.

/** Supported LLM vendors for the agent verb, derived from the schema enum. */
export const LLM_VENDORS = [
'openai',
'anthropic',
'google',
'vertex-gemini',
'vertex-openai',
'bedrock',
'deepseek',
'baseten',
'azure-openai',
'groq',
'huggingface',
] as const;

/** Union of LLM vendor ids accepted by the agent verb's `llm.vendor`. */
export type LlmVendor = (typeof LLM_VENDORS)[number];
11 changes: 3 additions & 8 deletions typescript/src/types/verbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
Target,
Vad,
} from './components.js';
import type { LlmVendor } from './llm-vendors.generated.js';

// ---------------------------------------------------------------------------
// Audio & Speech
Expand Down Expand Up @@ -161,14 +162,8 @@ export interface AgentLlmOptions {

/** `llm` block on the agent verb. */
export interface AgentLlm {
vendor:
| 'openai'
| 'anthropic'
| 'google'
| 'vertex-gemini'
| 'vertex-openai'
| 'bedrock'
| 'deepseek';
/** LLM vendor. Derived from the @jambonz/schema agent verb enum — see llm-vendors.generated.ts. */
vendor: LlmVendor;
model: string;
label?: string;
auth?: { apiKey?: string; [key: string]: unknown };
Expand Down
19 changes: 19 additions & 0 deletions typescript/test/schema-drift.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,23 @@ describe('Schema drift detection', () => {
});
}
});

// The agent verb's llm.vendor union is code-generated from the schema enum
// (src/types/llm-vendors.generated.ts, via scripts/gen-llm-vendors.mjs). The
// generated file is committed, so it can go stale if the @jambonz/schema dep is
// bumped without re-running `npm run gen:types`. This guards the committed artifact.
describe('generated LLM vendor list matches schema enum', () => {
it('LLM_VENDORS equals agent.schema.json llm.vendor.enum', async () => {
const agentSchema = JSON.parse(
readFileSync(resolve(verbsDir, 'agent.schema.json'), 'utf-8')
);
const schemaEnum: string[] = agentSchema.properties.llm.properties.vendor.enum;
const { LLM_VENDORS } = await import('../src/types/llm-vendors.generated.js');

expect(
[...LLM_VENDORS],
'generated LLM_VENDORS is stale — run `npm run gen:types`'
).toEqual(schemaEnum);
});
});
});