@finalai/agents 0.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.
Potentially problematic release.
This version of @finalai/agents might be problematic. Click here for more details.
- package/README.md +30 -0
- package/dist/modules/agent-client/acp-frames.d.mts +19 -0
- package/dist/modules/agent-client/acp-frames.mjs +49 -0
- package/dist/modules/agent-client/acp-frames.mjs.map +7 -0
- package/dist/modules/agent-client/acp.d.mts +43 -0
- package/dist/modules/agent-client/acp.mjs +76 -0
- package/dist/modules/agent-client/acp.mjs.map +7 -0
- package/dist/modules/agent-client/async-queue.d.mts +10 -0
- package/dist/modules/agent-client/async-queue.mjs +66 -0
- package/dist/modules/agent-client/async-queue.mjs.map +7 -0
- package/dist/modules/agent-client/build.d.mts +107 -0
- package/dist/modules/agent-client/build.mjs +222 -0
- package/dist/modules/agent-client/build.mjs.map +7 -0
- package/dist/modules/agent-client/client.d.mts +111 -0
- package/dist/modules/agent-client/client.mjs +198 -0
- package/dist/modules/agent-client/client.mjs.map +7 -0
- package/dist/modules/agent-client/collection-events.d.mts +35 -0
- package/dist/modules/agent-client/collection-events.mjs +142 -0
- package/dist/modules/agent-client/collection-events.mjs.map +7 -0
- package/dist/modules/agent-client/config-options.d.mts +24 -0
- package/dist/modules/agent-client/config-options.mjs +49 -0
- package/dist/modules/agent-client/config-options.mjs.map +7 -0
- package/dist/modules/agent-client/context.d.mts +31 -0
- package/dist/modules/agent-client/context.mjs +11 -0
- package/dist/modules/agent-client/context.mjs.map +7 -0
- package/dist/modules/agent-client/conversations.d.mts +385 -0
- package/dist/modules/agent-client/conversations.mjs +1156 -0
- package/dist/modules/agent-client/conversations.mjs.map +7 -0
- package/dist/modules/agent-client/errors.d.mts +85 -0
- package/dist/modules/agent-client/errors.mjs +94 -0
- package/dist/modules/agent-client/errors.mjs.map +7 -0
- package/dist/modules/agent-client/event-stream.d.mts +14 -0
- package/dist/modules/agent-client/event-stream.mjs +89 -0
- package/dist/modules/agent-client/event-stream.mjs.map +7 -0
- package/dist/modules/agent-client/frame-stream.d.mts +51 -0
- package/dist/modules/agent-client/frame-stream.mjs +333 -0
- package/dist/modules/agent-client/frame-stream.mjs.map +7 -0
- package/dist/modules/agent-client/http.d.mts +68 -0
- package/dist/modules/agent-client/http.mjs +168 -0
- package/dist/modules/agent-client/http.mjs.map +7 -0
- package/dist/modules/agent-client/index.d.mts +17 -0
- package/dist/modules/agent-client/index.mjs +86 -0
- package/dist/modules/agent-client/index.mjs.map +7 -0
- package/dist/modules/agent-client/live-stream.d.mts +18 -0
- package/dist/modules/agent-client/live-stream.mjs +94 -0
- package/dist/modules/agent-client/live-stream.mjs.map +7 -0
- package/dist/modules/agent-client/realtime.d.mts +72 -0
- package/dist/modules/agent-client/realtime.mjs +186 -0
- package/dist/modules/agent-client/realtime.mjs.map +7 -0
- package/dist/modules/agent-client/sandbox-uri.d.mts +19 -0
- package/dist/modules/agent-client/sandbox-uri.mjs +43 -0
- package/dist/modules/agent-client/sandbox-uri.mjs.map +7 -0
- package/dist/modules/agent-client/sessions.d.mts +124 -0
- package/dist/modules/agent-client/sessions.mjs +350 -0
- package/dist/modules/agent-client/sessions.mjs.map +7 -0
- package/dist/modules/agent-client/test-fakes.d.mts +40 -0
- package/dist/modules/agent-client/test-fakes.mjs +94 -0
- package/dist/modules/agent-client/test-fakes.mjs.map +7 -0
- package/dist/modules/authoring/approval.d.mts +1 -0
- package/dist/modules/authoring/approval.mjs +2 -0
- package/dist/modules/authoring/approval.mjs.map +7 -0
- package/dist/modules/authoring/channels.d.mts +1 -0
- package/dist/modules/authoring/channels.mjs +2 -0
- package/dist/modules/authoring/channels.mjs.map +7 -0
- package/dist/modules/authoring/final-channel.d.mts +1 -0
- package/dist/modules/authoring/final-channel.mjs +2 -0
- package/dist/modules/authoring/final-channel.mjs.map +7 -0
- package/dist/modules/authoring/hooks.d.mts +1 -0
- package/dist/modules/authoring/hooks.mjs +2 -0
- package/dist/modules/authoring/hooks.mjs.map +7 -0
- package/dist/modules/authoring/index.d.mts +1 -0
- package/dist/modules/authoring/index.mjs +2 -0
- package/dist/modules/authoring/index.mjs.map +7 -0
- package/dist/modules/authoring/instructions.d.mts +1 -0
- package/dist/modules/authoring/instructions.mjs +2 -0
- package/dist/modules/authoring/instructions.mjs.map +7 -0
- package/dist/modules/authoring/providers.d.mts +2 -0
- package/dist/modules/authoring/providers.mjs +1 -0
- package/dist/modules/authoring/providers.mjs.map +7 -0
- package/dist/modules/authoring/tools.d.mts +1 -0
- package/dist/modules/authoring/tools.mjs +2 -0
- package/dist/modules/authoring/tools.mjs.map +7 -0
- package/dist/modules/cli/build.d.mts +22 -0
- package/dist/modules/cli/build.mjs +77 -0
- package/dist/modules/cli/build.mjs.map +7 -0
- package/dist/modules/cli/channel-socket.d.mts +9 -0
- package/dist/modules/cli/channel-socket.mjs +114 -0
- package/dist/modules/cli/channel-socket.mjs.map +7 -0
- package/dist/modules/cli/cli.d.mts +16 -0
- package/dist/modules/cli/cli.mjs +151 -0
- package/dist/modules/cli/cli.mjs.map +7 -0
- package/dist/modules/cli/conformance.d.mts +36 -0
- package/dist/modules/cli/conformance.mjs +220 -0
- package/dist/modules/cli/conformance.mjs.map +7 -0
- package/dist/modules/cli/dev.d.mts +24 -0
- package/dist/modules/cli/dev.mjs +263 -0
- package/dist/modules/cli/dev.mjs.map +7 -0
- package/dist/modules/cli/files.d.mts +18 -0
- package/dist/modules/cli/files.mjs +66 -0
- package/dist/modules/cli/files.mjs.map +7 -0
- package/dist/modules/cli/harness-descriptions.d.mts +20 -0
- package/dist/modules/cli/harness-descriptions.mjs +70 -0
- package/dist/modules/cli/harness-descriptions.mjs.map +7 -0
- package/dist/modules/cli/main.d.mts +2 -0
- package/dist/modules/cli/main.mjs +20 -0
- package/dist/modules/cli/main.mjs.map +7 -0
- package/dist/modules/cli/output.d.mts +14 -0
- package/dist/modules/cli/output.mjs +94 -0
- package/dist/modules/cli/output.mjs.map +7 -0
- package/dist/modules/cli/publish.d.mts +14 -0
- package/dist/modules/cli/publish.mjs +89 -0
- package/dist/modules/cli/publish.mjs.map +7 -0
- package/dist/modules/compile.d.mts +3 -0
- package/dist/modules/compile.mjs +62 -0
- package/dist/modules/compile.mjs.map +7 -0
- package/dist/modules/harnesses.d.mts +1 -0
- package/dist/modules/harnesses.mjs +2 -0
- package/dist/modules/harnesses.mjs.map +7 -0
- package/dist/modules/index.browser.d.mts +4 -0
- package/dist/modules/index.browser.mjs +8 -0
- package/dist/modules/index.browser.mjs.map +7 -0
- package/dist/modules/index.d.mts +3 -0
- package/dist/modules/index.mjs +6 -0
- package/dist/modules/index.mjs.map +7 -0
- package/dist/modules/models.d.mts +1 -0
- package/dist/modules/models.mjs +2 -0
- package/dist/modules/models.mjs.map +7 -0
- package/dist/modules/public.d.mts +6 -0
- package/dist/modules/public.mjs +68 -0
- package/dist/modules/public.mjs.map +7 -0
- package/dist/modules/sandboxes.d.mts +1 -0
- package/dist/modules/sandboxes.mjs +2 -0
- package/dist/modules/sandboxes.mjs.map +7 -0
- package/examples/custom-harness/README.md +16 -0
- package/examples/custom-harness/agent.ts +154 -0
- package/examples/custom-sandbox/README.md +12 -0
- package/examples/custom-sandbox/agent.ts +14 -0
- package/examples/custom-sandbox/sandbox.ts +22 -0
- package/examples/release-scout/README.md +11 -0
- package/examples/release-scout/agent/agent.ts +22 -0
- package/examples/release-scout/agent/channels/github-signature.ts +27 -0
- package/examples/release-scout/agent/channels/github.ts +38 -0
- package/examples/release-scout/agent/connections/linear.ts +9 -0
- package/examples/release-scout/agent/hooks/audit.ts +24 -0
- package/examples/release-scout/agent/instructions/org-context.ts +26 -0
- package/examples/release-scout/agent/instructions.md +2 -0
- package/examples/release-scout/agent/sandbox.ts +20 -0
- package/examples/release-scout/agent/schedules/daily-digest.md +7 -0
- package/examples/release-scout/agent/skills/triage/SKILL.md +8 -0
- package/examples/release-scout/agent/skills/triage/checklist.md +3 -0
- package/examples/release-scout/agent/tools/lookup_release_notes.ts +24 -0
- package/examples/release-scout/client.ts +123 -0
- package/package.json +126 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import { createClient } from "../agent-client/client.mjs";
|
|
5
|
+
import { runBuild } from "./build.mjs";
|
|
6
|
+
import { runConformance } from "./conformance.mjs";
|
|
7
|
+
import { DEFAULT_DEV_PORT, runDev } from "./dev.mjs";
|
|
8
|
+
import { gitListFiles } from "./files.mjs";
|
|
9
|
+
import { runPublish } from "./publish.mjs";
|
|
10
|
+
const DEFAULT_AGENTS_HOST = "https://agent-session.final.ai/api";
|
|
11
|
+
const DEFAULT_COMMIT_MESSAGE = "Publish with final agents publish";
|
|
12
|
+
const USAGE = [
|
|
13
|
+
"usage: final-agents <command> [options]",
|
|
14
|
+
"",
|
|
15
|
+
"commands:",
|
|
16
|
+
" build [dir] compile the agent directory and report on its harness",
|
|
17
|
+
" dev [dir] serve its channel routes at http://localhost:<port>/<channel><path>",
|
|
18
|
+
" publish [dir] --agent <slug> commit the checkout to the agent and publish it",
|
|
19
|
+
" conformance <module> run the harness conformance kit against a harness the module exports",
|
|
20
|
+
"",
|
|
21
|
+
"options:",
|
|
22
|
+
" --host <url> agent platform API (default " + DEFAULT_AGENTS_HOST + ")",
|
|
23
|
+
" --agent <slug> publish: the agent definition (slug or id)",
|
|
24
|
+
" --ref <ref> publish: the ref to publish (GitHub-backed agents)",
|
|
25
|
+
" --message <text> publish: commit message",
|
|
26
|
+
" --note <text> publish: revision note",
|
|
27
|
+
` --port <port> dev: port to listen on (default ${DEFAULT_DEV_PORT})`,
|
|
28
|
+
' --export <name> conformance: the export to test (default "default"): a HarnessIface or { harness, dial }',
|
|
29
|
+
" --profile <name> conformance: final (default) or plain-acp-v2",
|
|
30
|
+
" --timeout <ms> conformance: per-case timeout",
|
|
31
|
+
" --json conformance: print the report as JSON",
|
|
32
|
+
"",
|
|
33
|
+
"FINAL_API_KEY authenticates (an organization API key). build checks compatibility only with it.",
|
|
34
|
+
`dev reads route secrets from AGENT_LOCAL_SECRETS_JSON ({"name": "value"}), limited to the manifest's secrets.`
|
|
35
|
+
].join("\n");
|
|
36
|
+
const envSchema = z.object({ FINAL_API_KEY: z.string().min(1).optional() });
|
|
37
|
+
const optionsConfig = {
|
|
38
|
+
host: { type: "string" },
|
|
39
|
+
agent: { type: "string" },
|
|
40
|
+
ref: { type: "string" },
|
|
41
|
+
message: { type: "string" },
|
|
42
|
+
note: { type: "string" },
|
|
43
|
+
port: { type: "string" },
|
|
44
|
+
export: { type: "string" },
|
|
45
|
+
profile: { type: "string" },
|
|
46
|
+
timeout: { type: "string" },
|
|
47
|
+
json: { type: "boolean" },
|
|
48
|
+
help: { type: "boolean", short: "h" }
|
|
49
|
+
};
|
|
50
|
+
const parseCommandLine = (argv) => parseArgs({ args: argv, options: optionsConfig, allowPositionals: true });
|
|
51
|
+
const parse = (argv) => {
|
|
52
|
+
try {
|
|
53
|
+
return { ok: true, parsed: parseCommandLine(argv) };
|
|
54
|
+
} catch (cause) {
|
|
55
|
+
return { ok: false, message: cause instanceof Error ? cause.message : String(cause) };
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const runAgentsCommand = async (deps) => {
|
|
59
|
+
const { output } = deps;
|
|
60
|
+
const parsedArgs = parse(deps.argv);
|
|
61
|
+
if (!parsedArgs.ok) {
|
|
62
|
+
output.stderr(`error: ${parsedArgs.message}`);
|
|
63
|
+
output.stderr(USAGE);
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
const { values, positionals } = parsedArgs.parsed;
|
|
67
|
+
const [command, dirArg, ...extra] = positionals;
|
|
68
|
+
if (values.help === true || command == null || command === "help") {
|
|
69
|
+
(command == null && values.help !== true ? output.stderr : output.stdout)(USAGE);
|
|
70
|
+
return command == null && values.help !== true ? 2 : 0;
|
|
71
|
+
}
|
|
72
|
+
if (extra.length > 0) {
|
|
73
|
+
output.stderr(`error: unexpected arguments: ${extra.join(" ")}`);
|
|
74
|
+
return 2;
|
|
75
|
+
}
|
|
76
|
+
const env = envSchema.safeParse(deps.env);
|
|
77
|
+
const apiKey = env.success ? env.data.FINAL_API_KEY ?? null : null;
|
|
78
|
+
const dir = resolve(deps.cwd, dirArg ?? ".");
|
|
79
|
+
const client = (key) => createClient({
|
|
80
|
+
host: values.host ?? DEFAULT_AGENTS_HOST,
|
|
81
|
+
auth: { apiKey: key },
|
|
82
|
+
fetch: deps.fetch
|
|
83
|
+
});
|
|
84
|
+
if (command === "build") {
|
|
85
|
+
return runBuild({ dir, client: apiKey == null ? null : client(apiKey), output });
|
|
86
|
+
}
|
|
87
|
+
if (command === "dev") {
|
|
88
|
+
const port = values.port == null ? DEFAULT_DEV_PORT : Number(values.port);
|
|
89
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
90
|
+
output.stderr(`error: --port must be a port number`);
|
|
91
|
+
return 2;
|
|
92
|
+
}
|
|
93
|
+
return runDev({
|
|
94
|
+
dir,
|
|
95
|
+
port,
|
|
96
|
+
env: deps.env,
|
|
97
|
+
output,
|
|
98
|
+
signal: deps.signal ?? new AbortController().signal
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (command === "publish") {
|
|
102
|
+
if (values.agent == null) {
|
|
103
|
+
output.stderr("error: publish needs --agent <slug>");
|
|
104
|
+
return 2;
|
|
105
|
+
}
|
|
106
|
+
if (apiKey == null) {
|
|
107
|
+
output.stderr("error: set FINAL_API_KEY to an organization API key");
|
|
108
|
+
return 2;
|
|
109
|
+
}
|
|
110
|
+
return runPublish({
|
|
111
|
+
dir,
|
|
112
|
+
agent: values.agent,
|
|
113
|
+
ref: values.ref ?? null,
|
|
114
|
+
message: values.message ?? DEFAULT_COMMIT_MESSAGE,
|
|
115
|
+
note: values.note ?? null,
|
|
116
|
+
client: client(apiKey),
|
|
117
|
+
listFiles: deps.listFiles ?? gitListFiles,
|
|
118
|
+
output
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (command === "conformance") {
|
|
122
|
+
if (dirArg == null) {
|
|
123
|
+
output.stderr("error: conformance needs <module>");
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
126
|
+
const timeoutMs = values.timeout == null ? null : Number(values.timeout);
|
|
127
|
+
if (timeoutMs != null && (!Number.isInteger(timeoutMs) || timeoutMs <= 0)) {
|
|
128
|
+
output.stderr("error: --timeout must be a positive number of milliseconds");
|
|
129
|
+
return 2;
|
|
130
|
+
}
|
|
131
|
+
return runConformance({
|
|
132
|
+
module: dirArg,
|
|
133
|
+
exportName: values.export ?? "default",
|
|
134
|
+
profile: values.profile ?? "final",
|
|
135
|
+
timeoutMs,
|
|
136
|
+
json: values.json === true,
|
|
137
|
+
cwd: deps.cwd,
|
|
138
|
+
output,
|
|
139
|
+
importModule: deps.importModule,
|
|
140
|
+
signal: deps.signal
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
output.stderr(`error: unknown command ${command}`);
|
|
144
|
+
output.stderr(USAGE);
|
|
145
|
+
return 2;
|
|
146
|
+
};
|
|
147
|
+
export {
|
|
148
|
+
DEFAULT_AGENTS_HOST,
|
|
149
|
+
runAgentsCommand
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=cli.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/cli/cli.ts"],
|
|
4
|
+
"sourcesContent": ["import { resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport * as z from 'zod';\nimport { createClient, type AgentClientIface } from \"../agent-client/client.mjs\";\nimport type { FetchIface } from \"../agent-client/http.mjs\";\nimport { runBuild } from \"./build.mjs\";\nimport { runConformance, type ImportModule } from \"./conformance.mjs\";\nimport { DEFAULT_DEV_PORT, runDev } from \"./dev.mjs\";\nimport { gitListFiles, type ListFiles } from \"./files.mjs\";\nimport type { OutputIface } from \"./output.mjs\";\nimport { runPublish } from \"./publish.mjs\";\n\n// Agent CLI commands:\n// `build` and `publish`, plus `dev` and `conformance`. Shipped as the `final-agents` bin until a general\n// `final` CLI exists; `runAgentsCommand` is what `final agents` would mount.\n\n// console-server's proxy to the agent platform API.\n// The session service accepts organization API keys; console-server's proxy is\n// for signed-in users only.\nexport const DEFAULT_AGENTS_HOST = 'https://agent-session.final.ai/api';\n\nconst DEFAULT_COMMIT_MESSAGE = 'Publish with final agents publish';\n\nconst USAGE = [\n 'usage: final-agents <command> [options]',\n '',\n 'commands:',\n ' build [dir] compile the agent directory and report on its harness',\n ' dev [dir] serve its channel routes at http://localhost:<port>/<channel><path>',\n ' publish [dir] --agent <slug> commit the checkout to the agent and publish it',\n ' conformance <module> run the harness conformance kit against a harness the module exports',\n '',\n 'options:',\n ' --host <url> agent platform API (default ' + DEFAULT_AGENTS_HOST + ')',\n ' --agent <slug> publish: the agent definition (slug or id)',\n ' --ref <ref> publish: the ref to publish (GitHub-backed agents)',\n ' --message <text> publish: commit message',\n ' --note <text> publish: revision note',\n ` --port <port> dev: port to listen on (default ${DEFAULT_DEV_PORT})`,\n ' --export <name> conformance: the export to test (default \"default\"): a HarnessIface or { harness, dial }',\n ' --profile <name> conformance: final (default) or plain-acp-v2',\n ' --timeout <ms> conformance: per-case timeout',\n ' --json conformance: print the report as JSON',\n '',\n 'FINAL_API_KEY authenticates (an organization API key). build checks compatibility only with it.',\n 'dev reads route secrets from AGENT_LOCAL_SECRETS_JSON ({\"name\": \"value\"}), limited to the manifest\\'s secrets.',\n].join('\\n');\n\nconst envSchema = z.object({ FINAL_API_KEY: z.string().min(1).optional() });\n\nexport interface AgentsCliDepsIface {\n argv: string[];\n env: Record<string, string | undefined>;\n cwd: string;\n output: OutputIface;\n // Seams: API requests and the checkout's file listing.\n fetch?: FetchIface;\n listFiles?: ListFiles;\n // Ends long-running commands (dev); main.ts aborts it on SIGINT and SIGTERM.\n signal?: AbortSignal;\n // Seam: loads the conformance module (dynamic import by file URL).\n importModule?: ImportModule;\n}\n\nconst optionsConfig = {\n host: { type: 'string' },\n agent: { type: 'string' },\n ref: { type: 'string' },\n message: { type: 'string' },\n note: { type: 'string' },\n port: { type: 'string' },\n export: { type: 'string' },\n profile: { type: 'string' },\n timeout: { type: 'string' },\n json: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n} as const;\n\nconst parseCommandLine = (argv: string[]) =>\n parseArgs({ args: argv, options: optionsConfig, allowPositionals: true });\n\ntype ParsedResult =\n | { ok: true; parsed: ReturnType<typeof parseCommandLine> }\n | { ok: false; message: string };\n\nconst parse = (argv: string[]): ParsedResult => {\n try {\n return { ok: true, parsed: parseCommandLine(argv) };\n } catch (cause) {\n return { ok: false, message: cause instanceof Error ? cause.message : String(cause) };\n }\n};\n\nexport const runAgentsCommand = async (deps: AgentsCliDepsIface): Promise<number> => {\n const { output } = deps;\n const parsedArgs = parse(deps.argv);\n if (!parsedArgs.ok) {\n output.stderr(`error: ${parsedArgs.message}`);\n output.stderr(USAGE);\n return 2;\n }\n const { values, positionals } = parsedArgs.parsed;\n const [command, dirArg, ...extra] = positionals;\n if (values.help === true || command == null || command === 'help') {\n (command == null && values.help !== true ? output.stderr : output.stdout)(USAGE);\n return command == null && values.help !== true ? 2 : 0;\n }\n if (extra.length > 0) {\n output.stderr(`error: unexpected arguments: ${extra.join(' ')}`);\n return 2;\n }\n const env = envSchema.safeParse(deps.env);\n const apiKey = env.success ? (env.data.FINAL_API_KEY ?? null) : null;\n const dir = resolve(deps.cwd, dirArg ?? '.');\n const client = (key: string): AgentClientIface =>\n createClient({\n host: values.host ?? DEFAULT_AGENTS_HOST,\n auth: { apiKey: key },\n fetch: deps.fetch,\n });\n\n if (command === 'build') {\n return runBuild({ dir, client: apiKey == null ? null : client(apiKey), output });\n }\n if (command === 'dev') {\n const port = values.port == null ? DEFAULT_DEV_PORT : Number(values.port);\n if (!Number.isInteger(port) || port < 0 || port > 65_535) {\n output.stderr(`error: --port must be a port number`);\n return 2;\n }\n return runDev({\n dir,\n port,\n env: deps.env,\n output,\n signal: deps.signal ?? new AbortController().signal,\n });\n }\n if (command === 'publish') {\n if (values.agent == null) {\n output.stderr('error: publish needs --agent <slug>');\n return 2;\n }\n if (apiKey == null) {\n output.stderr('error: set FINAL_API_KEY to an organization API key');\n return 2;\n }\n return runPublish({\n dir,\n agent: values.agent,\n ref: values.ref ?? null,\n message: values.message ?? DEFAULT_COMMIT_MESSAGE,\n note: values.note ?? null,\n client: client(apiKey),\n listFiles: deps.listFiles ?? gitListFiles,\n output,\n });\n }\n if (command === 'conformance') {\n if (dirArg == null) {\n output.stderr('error: conformance needs <module>');\n return 2;\n }\n const timeoutMs = values.timeout == null ? null : Number(values.timeout);\n if (timeoutMs != null && (!Number.isInteger(timeoutMs) || timeoutMs <= 0)) {\n output.stderr('error: --timeout must be a positive number of milliseconds');\n return 2;\n }\n return runConformance({\n module: dirArg,\n exportName: values.export ?? 'default',\n profile: values.profile ?? 'final',\n timeoutMs,\n json: values.json === true,\n cwd: deps.cwd,\n output,\n importModule: deps.importModule,\n signal: deps.signal,\n });\n }\n output.stderr(`error: unknown command ${command}`);\n output.stderr(USAGE);\n return 2;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type HarnessEndpointIface, type HarnessIface } from '@finalai/agent-provider';
|
|
2
|
+
import { type ConformanceStreamIface, type HarnessConformanceReportIface, type RunHarnessConformanceArgsIface } from '@finalai/agent-provider/conformance';
|
|
3
|
+
import * as z from 'zod';
|
|
4
|
+
import type { OutputIface } from "./output.mjs";
|
|
5
|
+
declare const moduleNamespaceSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
6
|
+
/** An imported module's exports, before the conformance export is parsed. */
|
|
7
|
+
export type ModuleNamespace = z.output<typeof moduleNamespaceSchema>;
|
|
8
|
+
type ModuleExport = ModuleNamespace[string];
|
|
9
|
+
export type ImportModule = (url: string) => Promise<ModuleNamespace>;
|
|
10
|
+
export interface ConformanceTargetIface {
|
|
11
|
+
harness: HarnessIface;
|
|
12
|
+
dial: RunHarnessConformanceArgsIface['dial'];
|
|
13
|
+
nativeSessionId?: RunHarnessConformanceArgsIface['nativeSessionId'];
|
|
14
|
+
}
|
|
15
|
+
export declare const dialHarnessEndpoint: (endpoint: HarnessEndpointIface) => Promise<ConformanceStreamIface>;
|
|
16
|
+
export type ConformanceTargetResult = {
|
|
17
|
+
ok: true;
|
|
18
|
+
target: ConformanceTargetIface;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
export declare const conformanceTargetOf: (value: ModuleExport) => ConformanceTargetResult;
|
|
24
|
+
export declare const reportLines: (report: HarnessConformanceReportIface) => string[];
|
|
25
|
+
export declare const runConformance: (args: {
|
|
26
|
+
module: string;
|
|
27
|
+
exportName: string;
|
|
28
|
+
profile: string;
|
|
29
|
+
timeoutMs: number | null;
|
|
30
|
+
json: boolean;
|
|
31
|
+
cwd: string;
|
|
32
|
+
output: OutputIface;
|
|
33
|
+
importModule?: ImportModule;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}) => Promise<number>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { createHttpStream } from "@agentclientprotocol/sdk/experimental/http-client";
|
|
2
|
+
import { ndJsonStream } from "@agentclientprotocol/sdk/experimental/v2";
|
|
3
|
+
import { createWebSocketStream } from "@agentclientprotocol/sdk/experimental/ws-client";
|
|
4
|
+
import {
|
|
5
|
+
AcpTransport
|
|
6
|
+
} from "@finalai/agent-provider";
|
|
7
|
+
import {
|
|
8
|
+
ConformanceCaseStatus,
|
|
9
|
+
ConformanceProfile,
|
|
10
|
+
conformanceOpenArgs,
|
|
11
|
+
runHarnessConformance
|
|
12
|
+
} from "@finalai/agent-provider/conformance";
|
|
13
|
+
import {
|
|
14
|
+
configureNodeClientWebSocket,
|
|
15
|
+
createNodeClientTlsSession
|
|
16
|
+
} from "@finalai/websockets/node-client-tls";
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
18
|
+
import { resolve } from "node:path";
|
|
19
|
+
import { pathToFileURL } from "node:url";
|
|
20
|
+
import WebSocket from "ws";
|
|
21
|
+
import * as z from "zod";
|
|
22
|
+
const moduleNamespaceSchema = z.record(z.string(), z.unknown());
|
|
23
|
+
const importModule = async (url) => moduleNamespaceSchema.parse(await import(
|
|
24
|
+
/* @vite-ignore */
|
|
25
|
+
url
|
|
26
|
+
));
|
|
27
|
+
const functionCheck = z.function();
|
|
28
|
+
const isFunction = (value) => functionCheck.safeParse(value).success;
|
|
29
|
+
const harnessMethodsSchema = z.looseObject({
|
|
30
|
+
describe: z.custom(isFunction),
|
|
31
|
+
open: z.custom(isFunction),
|
|
32
|
+
resume: z.custom(isFunction),
|
|
33
|
+
park: z.custom(isFunction),
|
|
34
|
+
close: z.custom(isFunction),
|
|
35
|
+
health: z.custom(isFunction)
|
|
36
|
+
});
|
|
37
|
+
const harnessSchema = z.custom(
|
|
38
|
+
(value) => harnessMethodsSchema.safeParse(isFunction(value) ? Object.assign({}, value) : value).success
|
|
39
|
+
);
|
|
40
|
+
const dialSchema = z.custom(
|
|
41
|
+
isFunction,
|
|
42
|
+
"`dial` must be a function"
|
|
43
|
+
);
|
|
44
|
+
const nativeSessionIdSchema = z.custom(
|
|
45
|
+
isFunction,
|
|
46
|
+
"`nativeSessionId` must be a function"
|
|
47
|
+
);
|
|
48
|
+
const targetFieldsSchema = z.looseObject({
|
|
49
|
+
harness: harnessSchema,
|
|
50
|
+
dial: dialSchema.optional(),
|
|
51
|
+
nativeSessionId: nativeSessionIdSchema.optional()
|
|
52
|
+
});
|
|
53
|
+
const dialHarnessEndpoint = async (endpoint) => {
|
|
54
|
+
switch (endpoint.transport) {
|
|
55
|
+
case AcpTransport.Http: {
|
|
56
|
+
const headers = { ...endpoint.headers, ...await endpoint.resolveHeaders?.() };
|
|
57
|
+
const tlsSession = endpoint.tls == null ? null : createNodeClientTlsSession({ tls: endpoint.tls });
|
|
58
|
+
const stream = createHttpStream(endpoint.url, {
|
|
59
|
+
headers,
|
|
60
|
+
cookies: "omit",
|
|
61
|
+
fetch: tlsSession?.fetch
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
...stream,
|
|
65
|
+
close: () => {
|
|
66
|
+
void stream.writable.close().catch(() => void 0);
|
|
67
|
+
void tlsSession?.close().catch(() => void 0);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
case AcpTransport.Ws: {
|
|
72
|
+
const headers = { ...endpoint.headers, ...await endpoint.resolveHeaders?.() };
|
|
73
|
+
const stream = createWebSocketStream(endpoint.url, {
|
|
74
|
+
cookies: "omit",
|
|
75
|
+
headers,
|
|
76
|
+
WebSocket: configureNodeClientWebSocket({ constructor: WebSocket, tls: endpoint.tls })
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
...stream,
|
|
80
|
+
close: () => void stream.writable.close().catch(() => void 0)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
case AcpTransport.Stdio: {
|
|
84
|
+
const child = spawn(endpoint.command, endpoint.args, {
|
|
85
|
+
env: { ...process.env, ...endpoint.env },
|
|
86
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
87
|
+
});
|
|
88
|
+
child.stdin.on("error", () => void 0);
|
|
89
|
+
let open = true;
|
|
90
|
+
const input = new ReadableStream({
|
|
91
|
+
start: (controller) => {
|
|
92
|
+
child.stdout.on("data", (chunk) => {
|
|
93
|
+
if (open) controller.enqueue(new Uint8Array(chunk));
|
|
94
|
+
});
|
|
95
|
+
child.stdout.on("end", () => {
|
|
96
|
+
if (!open) return;
|
|
97
|
+
open = false;
|
|
98
|
+
controller.close();
|
|
99
|
+
});
|
|
100
|
+
child.on("error", (error) => {
|
|
101
|
+
if (!open) return;
|
|
102
|
+
open = false;
|
|
103
|
+
controller.error(error);
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
cancel: () => {
|
|
107
|
+
open = false;
|
|
108
|
+
child.stdout.destroy();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const output = new WritableStream({
|
|
112
|
+
write: (chunk) => new Promise((done, fail) => {
|
|
113
|
+
child.stdin.write(chunk, (error) => error == null ? done() : fail(error));
|
|
114
|
+
}),
|
|
115
|
+
close: () => {
|
|
116
|
+
child.stdin.end();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
const stream = ndJsonStream(output, input);
|
|
120
|
+
return { ...stream, close: () => child.kill("SIGTERM") };
|
|
121
|
+
}
|
|
122
|
+
case AcpTransport.Relay:
|
|
123
|
+
throw new Error(
|
|
124
|
+
"relay endpoints are dialed by the session plane; export { harness, dial } to run them here"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const conformanceTargetOf = (value) => {
|
|
129
|
+
const harness = harnessSchema.safeParse(value);
|
|
130
|
+
if (harness.success)
|
|
131
|
+
return { ok: true, target: { harness: harness.data, dial: dialHarnessEndpoint } };
|
|
132
|
+
const fields = targetFieldsSchema.safeParse(value);
|
|
133
|
+
if (!fields.success) {
|
|
134
|
+
const issue = fields.error.issues.find((entry) => entry.path[0] !== "harness");
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
message: issue?.message ?? "the export must be a HarnessIface or { harness, dial?, nativeSessionId? }"
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
ok: true,
|
|
142
|
+
target: {
|
|
143
|
+
harness: fields.data.harness,
|
|
144
|
+
dial: fields.data.dial ?? dialHarnessEndpoint,
|
|
145
|
+
nativeSessionId: fields.data.nativeSessionId
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
const statusLabel = {
|
|
150
|
+
[ConformanceCaseStatus.Passed]: "pass",
|
|
151
|
+
[ConformanceCaseStatus.Failed]: "FAIL",
|
|
152
|
+
[ConformanceCaseStatus.Skipped]: "skip"
|
|
153
|
+
};
|
|
154
|
+
const reportLines = (report) => [
|
|
155
|
+
`profile: ${report.profile}`,
|
|
156
|
+
...report.cases.map((entry) => {
|
|
157
|
+
const why = entry.error ?? entry.detail;
|
|
158
|
+
return `${statusLabel[entry.status]} ${entry.name}${why == null ? "" : ` (${why})`}`;
|
|
159
|
+
}),
|
|
160
|
+
report.passed ? "conformance: passed" : "conformance: failed"
|
|
161
|
+
];
|
|
162
|
+
const runConformance = async (args) => {
|
|
163
|
+
const profile = z.nativeEnum(ConformanceProfile).safeParse(args.profile);
|
|
164
|
+
if (!profile.success) {
|
|
165
|
+
args.output.stderr(
|
|
166
|
+
`error: --profile must be ${Object.values(ConformanceProfile).join(" or ")}`
|
|
167
|
+
);
|
|
168
|
+
return 2;
|
|
169
|
+
}
|
|
170
|
+
const url = pathToFileURL(resolve(args.cwd, args.module)).href;
|
|
171
|
+
let loaded;
|
|
172
|
+
try {
|
|
173
|
+
loaded = await (args.importModule ?? importModule)(url);
|
|
174
|
+
} catch (cause) {
|
|
175
|
+
args.output.stderr(
|
|
176
|
+
`error: cannot load ${args.module}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
177
|
+
);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
if (!(args.exportName in loaded)) {
|
|
181
|
+
args.output.stderr(`error: ${args.module} has no export ${args.exportName}`);
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
const parsed = conformanceTargetOf(loaded[args.exportName]);
|
|
185
|
+
if (!parsed.ok) {
|
|
186
|
+
args.output.stderr(`error: ${args.module}#${args.exportName}: ${parsed.message}`);
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
const target = parsed.target;
|
|
190
|
+
const run = runHarnessConformance({
|
|
191
|
+
harness: target.harness,
|
|
192
|
+
dial: target.dial,
|
|
193
|
+
nativeSessionId: target.nativeSessionId,
|
|
194
|
+
profile: profile.data,
|
|
195
|
+
openArgs: conformanceOpenArgs(),
|
|
196
|
+
timeoutMs: args.timeoutMs ?? void 0,
|
|
197
|
+
cwd: args.cwd
|
|
198
|
+
});
|
|
199
|
+
const signal = args.signal;
|
|
200
|
+
const interrupted = signal == null ? null : new Promise((done) => {
|
|
201
|
+
if (signal.aborted) done(null);
|
|
202
|
+
else signal.addEventListener("abort", () => done(null), { once: true });
|
|
203
|
+
});
|
|
204
|
+
const report = await (interrupted == null ? run : Promise.race([run, interrupted]));
|
|
205
|
+
if (report == null) {
|
|
206
|
+
run.catch(() => void 0);
|
|
207
|
+
args.output.stderr("conformance: interrupted");
|
|
208
|
+
return 130;
|
|
209
|
+
}
|
|
210
|
+
if (args.json) args.output.stdout(JSON.stringify(report, null, 2));
|
|
211
|
+
else for (const line of reportLines(report)) args.output.stdout(line);
|
|
212
|
+
return report.passed ? 0 : 1;
|
|
213
|
+
};
|
|
214
|
+
export {
|
|
215
|
+
conformanceTargetOf,
|
|
216
|
+
dialHarnessEndpoint,
|
|
217
|
+
reportLines,
|
|
218
|
+
runConformance
|
|
219
|
+
};
|
|
220
|
+
//# sourceMappingURL=conformance.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/cli/conformance.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHttpStream } from '@agentclientprotocol/sdk/experimental/http-client';\nimport { ndJsonStream } from '@agentclientprotocol/sdk/experimental/v2';\nimport { createWebSocketStream } from '@agentclientprotocol/sdk/experimental/ws-client';\nimport {\n AcpTransport,\n type HarnessEndpointIface,\n type HarnessIface,\n} from '@finalai/agent-provider';\nimport {\n ConformanceCaseStatus,\n ConformanceProfile,\n conformanceOpenArgs,\n runHarnessConformance,\n type ConformanceStreamIface,\n type HarnessConformanceReportIface,\n type RunHarnessConformanceArgsIface,\n} from '@finalai/agent-provider/conformance';\nimport {\n configureNodeClientWebSocket,\n createNodeClientTlsSession,\n} from '@finalai/websockets/node-client-tls';\nimport { spawn } from 'node:child_process';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport WebSocket from 'ws';\nimport * as z from 'zod';\nimport type { OutputIface } from \"./output.mjs\";\n\n// `final-agents conformance <module>` exercises the public lifecycle and ACP\n// contracts of a harness exported by a module. The export is a HarnessIface, or `{ harness, dial }`\n// (with optional `nativeSessionId`) when its endpoints need a dialer of its own.\n\nconst moduleNamespaceSchema = z.record(z.string(), z.unknown());\n/** An imported module's exports, before the conformance export is parsed. */\nexport type ModuleNamespace = z.output<typeof moduleNamespaceSchema>;\ntype ModuleExport = ModuleNamespace[string];\n\nexport type ImportModule = (url: string) => Promise<ModuleNamespace>;\n\nconst importModule: ImportModule = async (url) =>\n moduleNamespaceSchema.parse(await import(/* @vite-ignore */ url));\n\nexport interface ConformanceTargetIface {\n harness: HarnessIface;\n dial: RunHarnessConformanceArgsIface['dial'];\n nativeSessionId?: RunHarnessConformanceArgsIface['nativeSessionId'];\n}\n\n// zod's function check, so functions from any realm qualify.\nconst functionCheck = z.function();\nconst isFunction = (value: ModuleExport): boolean => functionCheck.safeParse(value).success;\n\nconst harnessMethodsSchema = z.looseObject({\n describe: z.custom(isFunction),\n open: z.custom(isFunction),\n resume: z.custom(isFunction),\n park: z.custom(isFunction),\n close: z.custom(isFunction),\n health: z.custom(isFunction),\n});\n// An object with the methods, or a callable carrying them (what `defineHarness` returns).\nconst harnessSchema = z.custom<HarnessIface>(\n (value) =>\n harnessMethodsSchema.safeParse(isFunction(value) ? Object.assign({}, value) : value).success,\n);\nconst dialSchema = z.custom<ConformanceTargetIface['dial']>(\n isFunction,\n '`dial` must be a function',\n);\nconst nativeSessionIdSchema = z.custom<NonNullable<ConformanceTargetIface['nativeSessionId']>>(\n isFunction,\n '`nativeSessionId` must be a function',\n);\nconst targetFieldsSchema = z.looseObject({\n harness: harnessSchema,\n dial: dialSchema.optional(),\n nativeSessionId: nativeSessionIdSchema.optional(),\n});\n\n// Dials the endpoint kinds a harness outside Final can return: stdio programs,\n// Streamable HTTP and WebSocket, including header authentication and client TLS.\n// Relay endpoints are Final-managed.\nexport const dialHarnessEndpoint = async (\n endpoint: HarnessEndpointIface,\n): Promise<ConformanceStreamIface> => {\n switch (endpoint.transport) {\n case AcpTransport.Http: {\n const headers = { ...endpoint.headers, ...(await endpoint.resolveHeaders?.()) };\n const tlsSession =\n endpoint.tls == null ? null : createNodeClientTlsSession({ tls: endpoint.tls });\n const stream = createHttpStream(endpoint.url, {\n headers,\n cookies: 'omit',\n fetch: tlsSession?.fetch,\n });\n return {\n ...stream,\n close: () => {\n void stream.writable.close().catch(() => undefined);\n void tlsSession?.close().catch(() => undefined);\n },\n };\n }\n case AcpTransport.Ws: {\n const headers = { ...endpoint.headers, ...(await endpoint.resolveHeaders?.()) };\n const stream = createWebSocketStream(endpoint.url, {\n cookies: 'omit',\n headers,\n WebSocket: configureNodeClientWebSocket({ constructor: WebSocket, tls: endpoint.tls }),\n });\n return {\n ...stream,\n close: () => void stream.writable.close().catch(() => undefined),\n };\n }\n case AcpTransport.Stdio: {\n const child = spawn(endpoint.command, endpoint.args, {\n env: { ...process.env, ...endpoint.env },\n stdio: ['pipe', 'pipe', 'inherit'],\n });\n child.stdin.on('error', () => undefined);\n let open = true;\n const input = new ReadableStream<Uint8Array>({\n start: (controller) => {\n child.stdout.on('data', (chunk: Buffer) => {\n if (open) controller.enqueue(new Uint8Array(chunk));\n });\n child.stdout.on('end', () => {\n if (!open) return;\n open = false;\n controller.close();\n });\n child.on('error', (error) => {\n if (!open) return;\n open = false;\n controller.error(error);\n });\n },\n cancel: () => {\n open = false;\n child.stdout.destroy();\n },\n });\n const output = new WritableStream<Uint8Array>({\n write: (chunk) =>\n new Promise<void>((done, fail) => {\n child.stdin.write(chunk, (error) => (error == null ? done() : fail(error)));\n }),\n close: () => {\n child.stdin.end();\n },\n });\n const stream = ndJsonStream(output, input);\n return { ...stream, close: () => child.kill('SIGTERM') };\n }\n case AcpTransport.Relay:\n throw new Error(\n 'relay endpoints are dialed by the session plane; export { harness, dial } to run them here',\n );\n }\n};\n\nexport type ConformanceTargetResult =\n | { ok: true; target: ConformanceTargetIface }\n | { ok: false; message: string };\n\n// The export as a conformance target: a HarnessIface (dialed built in), or\n// `{ harness, dial?, nativeSessionId? }`.\nexport const conformanceTargetOf = (value: ModuleExport): ConformanceTargetResult => {\n const harness = harnessSchema.safeParse(value);\n if (harness.success)\n return { ok: true, target: { harness: harness.data, dial: dialHarnessEndpoint } };\n const fields = targetFieldsSchema.safeParse(value);\n if (!fields.success) {\n const issue = fields.error.issues.find((entry) => entry.path[0] !== 'harness');\n return {\n ok: false,\n message:\n issue?.message ??\n 'the export must be a HarnessIface or { harness, dial?, nativeSessionId? }',\n };\n }\n return {\n ok: true,\n target: {\n harness: fields.data.harness,\n dial: fields.data.dial ?? dialHarnessEndpoint,\n nativeSessionId: fields.data.nativeSessionId,\n },\n };\n};\n\nconst statusLabel = {\n [ConformanceCaseStatus.Passed]: 'pass',\n [ConformanceCaseStatus.Failed]: 'FAIL',\n [ConformanceCaseStatus.Skipped]: 'skip',\n} satisfies Record<ConformanceCaseStatus, string>;\n\nexport const reportLines = (report: HarnessConformanceReportIface): string[] => [\n `profile: ${report.profile}`,\n ...report.cases.map((entry) => {\n const why = entry.error ?? entry.detail;\n return `${statusLabel[entry.status]} ${entry.name}${why == null ? '' : ` (${why})`}`;\n }),\n report.passed ? 'conformance: passed' : 'conformance: failed',\n];\n\nexport const runConformance = async (args: {\n module: string;\n exportName: string;\n profile: string;\n timeoutMs: number | null;\n json: boolean;\n cwd: string;\n output: OutputIface;\n importModule?: ImportModule;\n // SIGINT/SIGTERM (main.ts): the kit has no cancellation, so the command stops\n // waiting for it and the process exits.\n signal?: AbortSignal;\n}): Promise<number> => {\n const profile = z.nativeEnum(ConformanceProfile).safeParse(args.profile);\n if (!profile.success) {\n args.output.stderr(\n `error: --profile must be ${Object.values(ConformanceProfile).join(' or ')}`,\n );\n return 2;\n }\n const url = pathToFileURL(resolve(args.cwd, args.module)).href;\n let loaded: ModuleNamespace;\n try {\n loaded = await (args.importModule ?? importModule)(url);\n } catch (cause) {\n args.output.stderr(\n `error: cannot load ${args.module}: ${cause instanceof Error ? cause.message : String(cause)}`,\n );\n return 1;\n }\n if (!(args.exportName in loaded)) {\n args.output.stderr(`error: ${args.module} has no export ${args.exportName}`);\n return 1;\n }\n const parsed = conformanceTargetOf(loaded[args.exportName]);\n if (!parsed.ok) {\n args.output.stderr(`error: ${args.module}#${args.exportName}: ${parsed.message}`);\n return 1;\n }\n const target = parsed.target;\n const run = runHarnessConformance({\n harness: target.harness,\n dial: target.dial,\n nativeSessionId: target.nativeSessionId,\n profile: profile.data,\n openArgs: conformanceOpenArgs(),\n timeoutMs: args.timeoutMs ?? undefined,\n cwd: args.cwd,\n });\n const signal = args.signal;\n const interrupted =\n signal == null\n ? null\n : new Promise<null>((done) => {\n if (signal.aborted) done(null);\n else signal.addEventListener('abort', () => done(null), { once: true });\n });\n const report = await (interrupted == null ? run : Promise.race([run, interrupted]));\n if (report == null) {\n run.catch(() => undefined);\n args.output.stderr('conformance: interrupted');\n return 130;\n }\n if (args.json) args.output.stdout(JSON.stringify(report, null, 2));\n else for (const line of reportLines(report)) args.output.stdout(line);\n return report.passed ? 0 : 1;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type OutputIface } from "./output.mjs";
|
|
2
|
+
export declare const DEFAULT_DEV_PORT = 3000;
|
|
3
|
+
export declare const parseLocalSecrets: (text: string | undefined) => Record<string, string> | null;
|
|
4
|
+
export interface DevServerArgsIface {
|
|
5
|
+
dir: string;
|
|
6
|
+
port: number;
|
|
7
|
+
hostname?: string;
|
|
8
|
+
secrets: Record<string, string>;
|
|
9
|
+
output: OutputIface;
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
}
|
|
12
|
+
export interface DevServerIface {
|
|
13
|
+
url: string;
|
|
14
|
+
reload(): Promise<boolean>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare const startDevServer: (args: DevServerArgsIface) => Promise<DevServerIface>;
|
|
18
|
+
export declare const runDev: (args: {
|
|
19
|
+
dir: string;
|
|
20
|
+
port: number;
|
|
21
|
+
env: Record<string, string | undefined>;
|
|
22
|
+
output: OutputIface;
|
|
23
|
+
signal: AbortSignal;
|
|
24
|
+
}) => Promise<number>;
|