@temporalio/agent-harness-codegen 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +75 -2
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +46 -0
- package/dist/src/generate.d.ts +38 -0
- package/dist/src/generate.js +233 -0
- package/package.json +31 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Temporal Technologies Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,76 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @temporalio/agent-harness-codegen
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Generate TypeScript application types from a Python or TypeScript harness agent's JSON Schema.
|
|
4
|
+
The generator is a public npm package starting with the next npm release; it was previously
|
|
5
|
+
repository-only tooling. No checkout or Bazel installation is needed to use the package.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --save-dev --save-exact @temporalio/agent-harness-codegen@0.2.0
|
|
9
|
+
# Export the schema using the SDK that defines your agent:
|
|
10
|
+
uv run temporal-agent-harness schema my_agent.workflow:OpenAIHelloAgentWorkflow -o agent.schema.json
|
|
11
|
+
# or: npx agent-harness-schema ./dist/agents.js#OpenAIHelloAgentTs -o agent.schema.json
|
|
12
|
+
npx harness-codegen agent.schema.json -o Agent.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The version above tracks this development branch and becomes installable when released.
|
|
16
|
+
Use the same release version as your other npm harness packages.
|
|
17
|
+
|
|
18
|
+
The output has an interface per model the agent exposes (its `@agent.accepts` input and output
|
|
19
|
+
models and its `agent.state(...)` types) and one mapping type naming them:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
export interface TicTacToeAgent {
|
|
23
|
+
handlers: {
|
|
24
|
+
new_game: { input: NewGame; output: TextReply };
|
|
25
|
+
play: { input: PlayMove; output: TextReply };
|
|
26
|
+
};
|
|
27
|
+
states: {
|
|
28
|
+
board: Board;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Handler inputs are typed as a sender writes them (a field with a default is optional); handler
|
|
34
|
+
outputs and states as they arrive on the wire (every field present). Tool inputs and outputs are
|
|
35
|
+
not typed.
|
|
36
|
+
|
|
37
|
+
Options: `-o <file>` writes to a file instead of stdout; `--name <TypeName>` renames the mapping
|
|
38
|
+
type, which defaults to the agent's workflow type name.
|
|
39
|
+
|
|
40
|
+
`--json-schemas` also writes `<TypeName>Schemas`: each handler's input and output as JSON Schema,
|
|
41
|
+
so you can check a message or a reply at runtime with any validator. Each schema is
|
|
42
|
+
self-contained (a `$ref` plus just the `$defs` it reaches), and the constant is `as const` for
|
|
43
|
+
tools that derive types from schemas. For example, with Ajv:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import Ajv from "ajv";
|
|
47
|
+
import { TicTacToeAgentSchemas } from "./TicTacToeAgent";
|
|
48
|
+
|
|
49
|
+
const validPlay = new Ajv().compile(TicTacToeAgentSchemas.play.input);
|
|
50
|
+
if (!validPlay(move)) console.warn(validPlay.errors);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
It is off by default, so the generated files examples commit stay as they are.
|
|
54
|
+
|
|
55
|
+
## The event protocol
|
|
56
|
+
|
|
57
|
+
With `--protocol`, it reads `temporal-agent-harness schema --protocol` instead and writes the
|
|
58
|
+
event stream's types. The client package (`typescript/packages/client`) commits that output as
|
|
59
|
+
its `src/protocol.ts`; `bazel run //typescript/packages/client/src:protocol_codegen_update` regenerates it.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
bazel test //typescript/packages/codegen/... # one target per test file, e.g. //typescript/packages/codegen/test:generate_test
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Without Bazel, from this directory:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
pnpm install
|
|
71
|
+
pnpm test # compiles src/ and test/ (including compile-time type checks), then runs node --test
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`test/golden/TicTacToeAgent.ts` is generated from the tic-tac-toe example's committed schema,
|
|
75
|
+
`python/examples/tictactoe/TicTacToeAgent.schema.json`;
|
|
76
|
+
`bazel run //typescript/packages/codegen/test/golden:golden_update` regenerates it.
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// harness-codegen <schema.json | -> [-o <out.ts>] [--name TypeName] [--json-schemas] [--protocol]
|
|
3
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { generateAgentTypes, generateProtocolTypes } from "./generate.js";
|
|
6
|
+
const USAGE = `usage: harness-codegen <schema.json | -> [-o <out.ts>] [--name <TypeName>] [--json-schemas] [--protocol]
|
|
7
|
+
|
|
8
|
+
Reads the document \`temporal-agent-harness schema module:Class\` prints (from a file, or
|
|
9
|
+
stdin for -) and writes TypeScript types for the agent: one interface per model, and a
|
|
10
|
+
mapping type naming each handler's input and output and each declared state. With
|
|
11
|
+
--json-schemas, also writes each handler's input and output JSON Schema as a constant.
|
|
12
|
+
|
|
13
|
+
With --protocol, reads \`temporal-agent-harness schema --protocol\` instead and writes the
|
|
14
|
+
event stream's types.`;
|
|
15
|
+
async function main() {
|
|
16
|
+
const { values, positionals } = parseArgs({
|
|
17
|
+
allowPositionals: true,
|
|
18
|
+
options: {
|
|
19
|
+
output: { type: "string", short: "o" },
|
|
20
|
+
name: { type: "string" },
|
|
21
|
+
protocol: { type: "boolean" },
|
|
22
|
+
"json-schemas": { type: "boolean" },
|
|
23
|
+
help: { type: "boolean", short: "h" }
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
if (values.help || positionals.length !== 1) {
|
|
27
|
+
console.error(USAGE);
|
|
28
|
+
process.exit(values.help ? 0 : 2);
|
|
29
|
+
}
|
|
30
|
+
const input = positionals[0] === "-" ? readFileSync(0, "utf8") : readFileSync(positionals[0], "utf8");
|
|
31
|
+
const doc = JSON.parse(input);
|
|
32
|
+
const source = values.protocol
|
|
33
|
+
? await generateProtocolTypes(doc)
|
|
34
|
+
: await generateAgentTypes(doc, {
|
|
35
|
+
typeName: values.name,
|
|
36
|
+
jsonSchemas: values["json-schemas"]
|
|
37
|
+
});
|
|
38
|
+
if (values.output === undefined)
|
|
39
|
+
process.stdout.write(source);
|
|
40
|
+
else
|
|
41
|
+
writeFileSync(values.output, source);
|
|
42
|
+
}
|
|
43
|
+
main().catch((error) => {
|
|
44
|
+
console.error(`harness-codegen: ${error instanceof Error ? error.message : String(error)}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type JSONSchema } from "json-schema-to-typescript";
|
|
2
|
+
/** The document `temporal-agent-harness schema module:Class` prints. */
|
|
3
|
+
export interface AgentSchemaDocument {
|
|
4
|
+
agent: string;
|
|
5
|
+
source: string;
|
|
6
|
+
/** Absent in documents from before init data existed; treated as `null`. */
|
|
7
|
+
init_data?: {
|
|
8
|
+
data: SchemaRef;
|
|
9
|
+
required: boolean;
|
|
10
|
+
} | null;
|
|
11
|
+
handlers: Record<string, {
|
|
12
|
+
description: string;
|
|
13
|
+
mid_turn: string;
|
|
14
|
+
input: SchemaRef;
|
|
15
|
+
output: SchemaRef;
|
|
16
|
+
}>;
|
|
17
|
+
states: Record<string, SchemaRef>;
|
|
18
|
+
$defs: Record<string, JSONSchema>;
|
|
19
|
+
}
|
|
20
|
+
/** The document `temporal-agent-harness schema --protocol` prints. */
|
|
21
|
+
export interface ProtocolSchemaDocument {
|
|
22
|
+
event: SchemaRef;
|
|
23
|
+
stream_items: SchemaRef[];
|
|
24
|
+
$defs: Record<string, JSONSchema>;
|
|
25
|
+
}
|
|
26
|
+
interface SchemaRef {
|
|
27
|
+
$ref: string;
|
|
28
|
+
}
|
|
29
|
+
export interface GenerateOptions {
|
|
30
|
+
/** Name of the mapping type. Defaults to the agent's workflow type name. */
|
|
31
|
+
typeName?: string;
|
|
32
|
+
/** Also emit `<typeName>Schemas`: each handler's input and output as JSON Schema, for
|
|
33
|
+
* validating them at runtime with any validator. */
|
|
34
|
+
jsonSchemas?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function generateAgentTypes(doc: AgentSchemaDocument, options?: GenerateOptions): Promise<string>;
|
|
37
|
+
export declare function generateProtocolTypes(doc: ProtocolSchemaDocument): Promise<string>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// TypeScript types from the documents `temporal-agent-harness schema` prints. For one agent:
|
|
2
|
+
// an interface per model in its `$defs`, one mapping type naming its init data, every
|
|
3
|
+
// handler's input and output and every declared state, and a same-named definition value that
|
|
4
|
+
// `startSession` takes. For the event protocol (`--protocol`): an interface
|
|
5
|
+
// per event payload and the envelope, plus the union of payloads and of their `type`s.
|
|
6
|
+
import { compile } from "json-schema-to-typescript";
|
|
7
|
+
const REF_PREFIX = "#/$defs/";
|
|
8
|
+
const ROOT = "HarnessCodegenRoot";
|
|
9
|
+
// Constraints json-schema-to-typescript drops, carried into the JSDoc instead.
|
|
10
|
+
const CONSTRAINT_KEYWORDS = [
|
|
11
|
+
"minimum",
|
|
12
|
+
"maximum",
|
|
13
|
+
"exclusiveMinimum",
|
|
14
|
+
"exclusiveMaximum",
|
|
15
|
+
"minLength",
|
|
16
|
+
"maxLength",
|
|
17
|
+
"pattern",
|
|
18
|
+
"minItems",
|
|
19
|
+
"maxItems"
|
|
20
|
+
];
|
|
21
|
+
// Keywords whose values are data rather than schemas, so they are copied as they are.
|
|
22
|
+
const DATA_KEYWORDS = new Set(["default", "examples", "const", "enum"]);
|
|
23
|
+
export async function generateAgentTypes(doc, options = {}) {
|
|
24
|
+
const names = typeNames(Object.keys(doc.$defs));
|
|
25
|
+
const typeName = options.typeName ?? safeName(doc.agent);
|
|
26
|
+
for (const name of options.jsonSchemas ? [typeName, `${typeName}Schemas`] : [typeName]) {
|
|
27
|
+
if ([...names.values()].includes(name)) {
|
|
28
|
+
throw new Error(`the generated name ${name} is also the name of one of the agent's models; ` +
|
|
29
|
+
"pass a different type name");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return [
|
|
33
|
+
`// GENERATED by @temporal-agent-harness/codegen from ${doc.source}. Do not edit.`,
|
|
34
|
+
"",
|
|
35
|
+
await compileModels(doc.$defs, names),
|
|
36
|
+
"",
|
|
37
|
+
mappingType(doc, typeName, names),
|
|
38
|
+
"",
|
|
39
|
+
definitionValue(doc, typeName),
|
|
40
|
+
...(options.jsonSchemas ? ["", handlerSchemas(doc, typeName)] : []),
|
|
41
|
+
""
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
/** Every handler's input and output as a self-contained JSON Schema: a `$ref` to its model,
|
|
45
|
+
* with `$defs` holding just the models that one reaches. */
|
|
46
|
+
function handlerSchemas(doc, typeName) {
|
|
47
|
+
const standalone = (ref) => {
|
|
48
|
+
const $defs = {};
|
|
49
|
+
const visit = (node) => {
|
|
50
|
+
if (Array.isArray(node))
|
|
51
|
+
return node.forEach(visit);
|
|
52
|
+
if (node === null || typeof node !== "object")
|
|
53
|
+
return;
|
|
54
|
+
for (const [key, value] of Object.entries(node)) {
|
|
55
|
+
if (key !== "$ref" || typeof value !== "string")
|
|
56
|
+
visit(value);
|
|
57
|
+
else if (value.startsWith(REF_PREFIX) && !(value.slice(REF_PREFIX.length) in $defs)) {
|
|
58
|
+
const name = value.slice(REF_PREFIX.length);
|
|
59
|
+
$defs[name] = doc.$defs[name];
|
|
60
|
+
visit($defs[name]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
visit(ref);
|
|
65
|
+
return { $ref: ref.$ref, $defs };
|
|
66
|
+
};
|
|
67
|
+
const schemas = Object.fromEntries(Object.entries(doc.handlers).map(([name, h]) => [name, { input: standalone(h.input), output: standalone(h.output) }]));
|
|
68
|
+
return [
|
|
69
|
+
`/** Each \`${doc.agent}\` handler's input and output as JSON Schema, to validate them with any`,
|
|
70
|
+
" * JSON Schema validator. */",
|
|
71
|
+
`export const ${typeName}Schemas = ${JSON.stringify(schemas, null, 2)} as const;`
|
|
72
|
+
].join("\n");
|
|
73
|
+
}
|
|
74
|
+
export async function generateProtocolTypes(doc) {
|
|
75
|
+
const names = typeNames(Object.keys(doc.$defs));
|
|
76
|
+
const resolve = refResolver(names);
|
|
77
|
+
const items = doc.stream_items.map(resolve);
|
|
78
|
+
return [
|
|
79
|
+
"// GENERATED by @temporal-agent-harness/codegen from the harness event protocol",
|
|
80
|
+
"// (`temporal-agent-harness schema --protocol`). Do not edit.",
|
|
81
|
+
"",
|
|
82
|
+
await compileModels(doc.$defs, names),
|
|
83
|
+
"",
|
|
84
|
+
`/** Every payload an \`${resolve(doc.event)}\` can carry, discriminated by \`type\`. */`,
|
|
85
|
+
`export type AgentStreamItem =\n${items.map((name) => ` | ${name}`).join("\n")};`,
|
|
86
|
+
"",
|
|
87
|
+
"/** The `type` of every event on the stream. */",
|
|
88
|
+
'export type AgentEventType = AgentStreamItem["type"];',
|
|
89
|
+
""
|
|
90
|
+
].join("\n");
|
|
91
|
+
}
|
|
92
|
+
async function compileModels($defs, names) {
|
|
93
|
+
const resolve = refResolver(names);
|
|
94
|
+
const defs = {};
|
|
95
|
+
for (const [key, schema] of Object.entries($defs)) {
|
|
96
|
+
defs[key] = { ...prepare(schema, resolve), title: names.get(key) };
|
|
97
|
+
}
|
|
98
|
+
const root = {
|
|
99
|
+
title: ROOT,
|
|
100
|
+
anyOf: Object.keys(defs).map((key) => ({ $ref: `${REF_PREFIX}${key}` })),
|
|
101
|
+
$defs: defs
|
|
102
|
+
};
|
|
103
|
+
const models = await compile(root, ROOT, {
|
|
104
|
+
bannerComment: "",
|
|
105
|
+
additionalProperties: false,
|
|
106
|
+
unknownAny: true,
|
|
107
|
+
strictIndexSignatures: true
|
|
108
|
+
});
|
|
109
|
+
return dropRoot(models).trim();
|
|
110
|
+
}
|
|
111
|
+
/** Shape a model's schema for json-schema-to-typescript, which would otherwise mint extra
|
|
112
|
+
* named types: one per property-level title, and one per `$ref` that carries a sibling
|
|
113
|
+
* keyword (pydantic puts a property's description next to its `$ref`). Titles are dropped
|
|
114
|
+
* and each `$ref` becomes the referenced type's name, keeping the description. Constraints
|
|
115
|
+
* are folded into the description. */
|
|
116
|
+
function prepare(node, resolve) {
|
|
117
|
+
if (Array.isArray(node))
|
|
118
|
+
return node.map((item) => prepare(item, resolve));
|
|
119
|
+
if (node === null || typeof node !== "object")
|
|
120
|
+
return node;
|
|
121
|
+
if (typeof node.$ref === "string") {
|
|
122
|
+
const { description } = node;
|
|
123
|
+
return {
|
|
124
|
+
tsType: resolve(node),
|
|
125
|
+
...(typeof description === "string" ? { description: fromSphinx(description) } : {})
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const out = {};
|
|
129
|
+
for (const [key, value] of Object.entries(node)) {
|
|
130
|
+
if (key === "title")
|
|
131
|
+
continue;
|
|
132
|
+
if (DATA_KEYWORDS.has(key))
|
|
133
|
+
out[key] = value;
|
|
134
|
+
else if (key === "properties" || key === "$defs" || key === "patternProperties")
|
|
135
|
+
out[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [k, prepare(v, resolve)]));
|
|
136
|
+
else
|
|
137
|
+
out[key] = prepare(value, resolve);
|
|
138
|
+
}
|
|
139
|
+
if (typeof out.description === "string")
|
|
140
|
+
out.description = fromSphinx(out.description);
|
|
141
|
+
const tags = CONSTRAINT_KEYWORDS.filter((k) => k in out).map((k) => `@${k} ${String(out[k])}`);
|
|
142
|
+
if (tags.length > 0) {
|
|
143
|
+
const description = typeof out.description === "string" ? `${out.description}\n\n` : "";
|
|
144
|
+
out.description = description + tags.join("\n");
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
/** Python docstrings use reStructuredText roles; JSDoc renders Markdown. */
|
|
149
|
+
function fromSphinx(text) {
|
|
150
|
+
return text
|
|
151
|
+
.replace(/:[a-z]+:`~?([^`]+)`/g, "`$1`")
|
|
152
|
+
.replace(/``([^`]+)``/g, "`$1`");
|
|
153
|
+
}
|
|
154
|
+
function refResolver(names) {
|
|
155
|
+
return (ref) => {
|
|
156
|
+
const key = ref.$ref.startsWith(REF_PREFIX) ? ref.$ref.slice(REF_PREFIX.length) : undefined;
|
|
157
|
+
const name = key === undefined ? undefined : names.get(key);
|
|
158
|
+
if (name === undefined)
|
|
159
|
+
throw new Error(`unresolvable $ref ${ref.$ref}`);
|
|
160
|
+
return name;
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function mappingType(doc, typeName, names) {
|
|
164
|
+
const resolve = refResolver(names);
|
|
165
|
+
const handlers = Object.entries(doc.handlers).map(([name, h]) => `${jsdoc(h.description, " ")} ${name}: { input: ${resolve(h.input)}; output: ${resolve(h.output)} };`);
|
|
166
|
+
const states = Object.entries(doc.states).map(([id, ref]) => ` ${id}: ${resolve(ref)};`);
|
|
167
|
+
const block = (lines) => lines.length === 0 ? "Record<never, never>" : `{\n${lines.join("\n")}\n }`;
|
|
168
|
+
const initData = doc.init_data
|
|
169
|
+
? `{ data: ${resolve(doc.init_data.data)}; required: ${doc.init_data.required} }`
|
|
170
|
+
: "null";
|
|
171
|
+
return [
|
|
172
|
+
`/** The \`${doc.agent}\` agent's init data, handlers (message in, reply out) and observable`,
|
|
173
|
+
" * state. */",
|
|
174
|
+
`export interface ${typeName} {`,
|
|
175
|
+
` initData: ${initData};`,
|
|
176
|
+
` handlers: ${block(handlers)};`,
|
|
177
|
+
` states: ${block(states)};`,
|
|
178
|
+
"}"
|
|
179
|
+
].join("\n");
|
|
180
|
+
}
|
|
181
|
+
/** The value `startSession` takes, named like the mapping type (a type and a value may share a
|
|
182
|
+
* name). Its type carries the mapping type as a phantom `schema`, so the file needs no import. */
|
|
183
|
+
function definitionValue(doc, typeName) {
|
|
184
|
+
const workflowType = JSON.stringify(doc.agent);
|
|
185
|
+
return [
|
|
186
|
+
`/** Pass to \`startSession\` to start a \`${doc.agent}\` session. */`,
|
|
187
|
+
`export const ${typeName}: { readonly workflowType: ${workflowType}; readonly schema?: ${typeName} } = {`,
|
|
188
|
+
` workflowType: ${workflowType}`,
|
|
189
|
+
"};"
|
|
190
|
+
].join("\n");
|
|
191
|
+
}
|
|
192
|
+
function jsdoc(text, indent) {
|
|
193
|
+
const lines = text.trim().replaceAll("*/", "*\\/").split("\n");
|
|
194
|
+
if (lines.length === 0 || lines[0] === "")
|
|
195
|
+
return "";
|
|
196
|
+
if (lines.length === 1)
|
|
197
|
+
return `${indent}/** ${lines[0]} */\n`;
|
|
198
|
+
return `${indent}/**\n${lines.map((l) => `${indent} * ${l}`.trimEnd()).join("\n")}\n${indent} */\n`;
|
|
199
|
+
}
|
|
200
|
+
/** The generated name for every `$defs` key, rejecting two keys that would share one. */
|
|
201
|
+
function typeNames(keys) {
|
|
202
|
+
const names = new Map();
|
|
203
|
+
const owners = new Map();
|
|
204
|
+
for (const key of keys) {
|
|
205
|
+
const name = safeName(key);
|
|
206
|
+
const other = owners.get(name);
|
|
207
|
+
if (other !== undefined) {
|
|
208
|
+
throw new Error(`models ${other} and ${key} would both be generated as ${name}`);
|
|
209
|
+
}
|
|
210
|
+
owners.set(name, key);
|
|
211
|
+
names.set(key, name);
|
|
212
|
+
}
|
|
213
|
+
return names;
|
|
214
|
+
}
|
|
215
|
+
/** A TypeScript identifier for a schema name: `Echo-Input` → `EchoInput`. Already-safe
|
|
216
|
+
* PascalCase names come back unchanged, which json-schema-to-typescript also leaves alone. */
|
|
217
|
+
function safeName(raw) {
|
|
218
|
+
const name = raw
|
|
219
|
+
.split(/[^A-Za-z0-9]+/)
|
|
220
|
+
.filter(Boolean)
|
|
221
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
222
|
+
.join("");
|
|
223
|
+
if (!/^[A-Za-z]/.test(name))
|
|
224
|
+
throw new Error(`cannot derive a type name from ${raw}`);
|
|
225
|
+
return name;
|
|
226
|
+
}
|
|
227
|
+
function dropRoot(source) {
|
|
228
|
+
const start = source.indexOf(`export type ${ROOT} =`);
|
|
229
|
+
if (start === -1)
|
|
230
|
+
return source;
|
|
231
|
+
const end = source.indexOf(";\n", start);
|
|
232
|
+
return source.slice(0, start) + source.slice(end + 2);
|
|
233
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@temporalio/agent-harness-codegen",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Generate TypeScript types for a temporal-agent-harness agent from its `temporal-agent-harness schema` document.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"harness-codegen": "dist/src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"json-schema-to-typescript": "^16.0.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@types/node": "^22.20.4",
|
|
14
|
+
"typescript": "^7.0.2"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/src"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org/"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/temporal-community/temporal-agent-harness.git",
|
|
27
|
+
"directory": "typescript/packages/codegen"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json --outDir dist",
|
|
31
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
|
+
"test": "tsc -p tsconfig.json --outDir dist && node --test dist/test/*.test.js"
|
|
33
|
+
}
|
|
6
34
|
}
|