@maxanstey-meridian/tandem 0.2.0 → 0.3.1

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.
Files changed (79) hide show
  1. package/README.md +111 -21
  2. package/dist/index.js +39 -50
  3. package/dist/runtime/darwin-arm64/Dapper.dll +0 -0
  4. package/dist/runtime/darwin-arm64/Spectre.Console.Json.dll +0 -0
  5. package/dist/runtime/darwin-arm64/Tandem.Advanced.dll +0 -0
  6. package/dist/runtime/darwin-arm64/{Tandem.NodeApiSpike.Bridge.cjs → Tandem.Bridge.cjs} +1 -1
  7. package/dist/runtime/{linux-x64/Tandem.NodeApiSpike.Bridge.d.ts → darwin-arm64/Tandem.Bridge.d.ts} +8 -2
  8. package/dist/runtime/darwin-arm64/{Tandem.NodeApiSpike.Bridge.deps.json → Tandem.Bridge.deps.json} +93 -294
  9. package/dist/runtime/darwin-arm64/Tandem.Bridge.dll +0 -0
  10. package/dist/runtime/{linux-x64/Tandem.NodeApiSpike.Bridge.mjs → darwin-arm64/Tandem.Bridge.mjs} +1 -1
  11. package/dist/runtime/darwin-arm64/Tandem.Ledger.dll +0 -0
  12. package/dist/runtime/darwin-arm64/Tandem.OpenAICompatible.dll +0 -0
  13. package/dist/runtime/darwin-arm64/Tandem.Terminal.dll +0 -0
  14. package/dist/runtime/darwin-arm64/Tandem.dll +0 -0
  15. package/dist/runtime/linux-x64/Dapper.dll +0 -0
  16. package/dist/runtime/linux-x64/Spectre.Console.Json.dll +0 -0
  17. package/dist/runtime/linux-x64/Tandem.Advanced.dll +0 -0
  18. package/dist/runtime/linux-x64/{Tandem.NodeApiSpike.Bridge.cjs → Tandem.Bridge.cjs} +1 -1
  19. package/dist/runtime/{darwin-arm64/Tandem.NodeApiSpike.Bridge.d.ts → linux-x64/Tandem.Bridge.d.ts} +8 -2
  20. package/dist/runtime/linux-x64/{Tandem.NodeApiSpike.Bridge.deps.json → Tandem.Bridge.deps.json} +93 -294
  21. package/dist/runtime/linux-x64/Tandem.Bridge.dll +0 -0
  22. package/dist/runtime/{darwin-arm64/Tandem.NodeApiSpike.Bridge.mjs → linux-x64/Tandem.Bridge.mjs} +1 -1
  23. package/dist/runtime/linux-x64/Tandem.Ledger.dll +0 -0
  24. package/dist/runtime/linux-x64/Tandem.OpenAICompatible.dll +0 -0
  25. package/dist/runtime/linux-x64/Tandem.Terminal.dll +0 -0
  26. package/dist/runtime/linux-x64/Tandem.dll +0 -0
  27. package/dist/runtime/loader.mjs +2 -2
  28. package/package.json +2 -1
  29. package/dist/runtime/darwin-arm64/Humanizer.dll +0 -0
  30. package/dist/runtime/darwin-arm64/Json.More.dll +0 -0
  31. package/dist/runtime/darwin-arm64/JsonPointer.Net.dll +0 -0
  32. package/dist/runtime/darwin-arm64/JsonSchema.Net.Generation.dll +0 -0
  33. package/dist/runtime/darwin-arm64/JsonSchema.Net.dll +0 -0
  34. package/dist/runtime/darwin-arm64/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  35. package/dist/runtime/darwin-arm64/Microsoft.CodeAnalysis.dll +0 -0
  36. package/dist/runtime/darwin-arm64/Microsoft.JavaScript.NodeApi.Generator.dll +0 -0
  37. package/dist/runtime/darwin-arm64/System.Reflection.MetadataLoadContext.dll +0 -0
  38. package/dist/runtime/darwin-arm64/Tandem.NodeApiSpike.Bridge.dll +0 -0
  39. package/dist/runtime/darwin-arm64/cs/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  40. package/dist/runtime/darwin-arm64/cs/Microsoft.CodeAnalysis.resources.dll +0 -0
  41. package/dist/runtime/darwin-arm64/de/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  42. package/dist/runtime/darwin-arm64/de/Microsoft.CodeAnalysis.resources.dll +0 -0
  43. package/dist/runtime/darwin-arm64/es/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  44. package/dist/runtime/darwin-arm64/es/Microsoft.CodeAnalysis.resources.dll +0 -0
  45. package/dist/runtime/darwin-arm64/fr/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  46. package/dist/runtime/darwin-arm64/fr/Microsoft.CodeAnalysis.resources.dll +0 -0
  47. package/dist/runtime/darwin-arm64/import.cjs +0 -3
  48. package/dist/runtime/darwin-arm64/it/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  49. package/dist/runtime/darwin-arm64/it/Microsoft.CodeAnalysis.resources.dll +0 -0
  50. package/dist/runtime/darwin-arm64/ja/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  51. package/dist/runtime/darwin-arm64/ja/Microsoft.CodeAnalysis.resources.dll +0 -0
  52. package/dist/runtime/darwin-arm64/ko/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  53. package/dist/runtime/darwin-arm64/ko/Microsoft.CodeAnalysis.resources.dll +0 -0
  54. package/dist/runtime/darwin-arm64/pl/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  55. package/dist/runtime/darwin-arm64/pl/Microsoft.CodeAnalysis.resources.dll +0 -0
  56. package/dist/runtime/darwin-arm64/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  57. package/dist/runtime/darwin-arm64/pt-BR/Microsoft.CodeAnalysis.resources.dll +0 -0
  58. package/dist/runtime/darwin-arm64/ru/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  59. package/dist/runtime/darwin-arm64/ru/Microsoft.CodeAnalysis.resources.dll +0 -0
  60. package/dist/runtime/darwin-arm64/tr/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  61. package/dist/runtime/darwin-arm64/tr/Microsoft.CodeAnalysis.resources.dll +0 -0
  62. package/dist/runtime/darwin-arm64/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  63. package/dist/runtime/darwin-arm64/zh-Hans/Microsoft.CodeAnalysis.resources.dll +0 -0
  64. package/dist/runtime/darwin-arm64/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll +0 -0
  65. package/dist/runtime/darwin-arm64/zh-Hant/Microsoft.CodeAnalysis.resources.dll +0 -0
  66. package/dist/runtime/linux-x64/Humanizer.dll +0 -0
  67. package/dist/runtime/linux-x64/Json.More.dll +0 -0
  68. package/dist/runtime/linux-x64/JsonPointer.Net.dll +0 -0
  69. package/dist/runtime/linux-x64/JsonSchema.Net.Generation.dll +0 -0
  70. package/dist/runtime/linux-x64/JsonSchema.Net.dll +0 -0
  71. package/dist/runtime/linux-x64/Microsoft.CodeAnalysis.CSharp.dll +0 -0
  72. package/dist/runtime/linux-x64/Microsoft.CodeAnalysis.dll +0 -0
  73. package/dist/runtime/linux-x64/Microsoft.JavaScript.NodeApi.Generator.dll +0 -0
  74. package/dist/runtime/linux-x64/System.Reflection.MetadataLoadContext.dll +0 -0
  75. package/dist/runtime/linux-x64/Tandem.NodeApiSpike.Bridge.dll +0 -0
  76. package/dist/runtime/linux-x64/import.cjs +0 -3
  77. package/dist/runtime/loader.d.mts +0 -10
  78. /package/dist/runtime/darwin-arm64/{Tandem.NodeApiSpike.Bridge.runtimeconfig.json → Tandem.Bridge.runtimeconfig.json} +0 -0
  79. /package/dist/runtime/linux-x64/{Tandem.NodeApiSpike.Bridge.runtimeconfig.json → Tandem.Bridge.runtimeconfig.json} +0 -0
package/README.md CHANGED
@@ -56,29 +56,19 @@ const result = await run(normalizeInput, { input: " Hello ", normalized: null
56
56
  console.log(result.state.normalized);
57
57
  ```
58
58
 
59
- State holds application facts, participants perform work, and routes decide what runs next. See the
60
- [examples](https://github.com/maxanstey-meridian/tandem-ts/tree/main/examples) for agents,
61
- capabilities, interactions, persistence, and complete examples.
62
-
63
- Licensed under the [MIT License](./LICENSE).
64
-
65
- For a specialised model that requires a plain user message, a raw-output agent
66
- can set both `instructions` and `output.instructions` to `""`. Tandem then sends
67
- the authored message without system instructions or a response-format constraint.
68
- Other agent/output modes still require nonblank instructions.
69
-
70
- ## Development and publishing
59
+ The package is ESM-only, and a project created by `npm init` is CommonJS, so save the code as
60
+ `index.mts`:
71
61
 
72
62
  ```sh
73
- pnpm install
74
- pnpm build
75
- pnpm test
76
- npm pack
63
+ node index.mts
77
64
  ```
78
65
 
79
- `npm pack` and `npm publish` build fresh output before packaging. The npm package contains
80
- `dist`, including the vendored platform bridge bundles in `dist/runtime`. Update each runtime
81
- bundle from a tested Tandem bridge build before releasing runtime changes.
66
+ To use `index.ts` instead, first mark the project as ESM with `npm pkg set type=module`, then run
67
+ `node index.ts`. Node.js 22.18 or newer runs TypeScript directly by stripping types; on older
68
+ Node.js 22 releases, use `npx tsx index.mts`. The program prints `hello`.
69
+
70
+ State holds application facts, participants perform work, and routes decide what runs next. See the
71
+ [examples](#examples) for routing, persistence and complete model-backed pipelines.
82
72
 
83
73
  ## Runtime-sized collections
84
74
 
@@ -94,6 +84,33 @@ completion and sibling parallel agent calls are rejected. Operational failure
94
84
  fails the collection, drains active work, and skips application of results.
95
85
 
96
86
  ```ts
87
+ import { collection, taskAgent, type ChatClient } from "@maxanstey-meridian/tandem";
88
+ import { z } from "zod";
89
+
90
+ const client = {
91
+ kind: "openai-compatible",
92
+ version: 1,
93
+ endpoint: "https://openrouter.ai/api/v1",
94
+ model: "deepseek/deepseek-v4-flash-0731",
95
+ wireApi: "completions",
96
+ apiKeyEnvironmentVariable: "OPENROUTER_API_KEY",
97
+ } as const satisfies ChatClient;
98
+
99
+ const Proposition = z.string();
100
+ const Claim = z.object({ subject: z.string(), statement: z.string() });
101
+ const SourceState = z.object({ propositions: z.array(Proposition), claims: z.array(Claim) });
102
+ type SourceState = z.infer<typeof SourceState>;
103
+
104
+ const canonicaliser = taskAgent({
105
+ id: "canonicaliser",
106
+ instructions: "Restate the proposition as one canonical claim.",
107
+ client,
108
+ input: Proposition,
109
+ result: Claim,
110
+ message: (proposition) => proposition,
111
+ output: { instructions: "Return the claim's subject and statement.", schema: Claim },
112
+ });
113
+
97
114
  const canonicalise = collection({
98
115
  id: "canonicalise",
99
116
  item: Proposition,
@@ -102,11 +119,13 @@ const canonicalise = collection({
102
119
  max: 6,
103
120
  items: (state: SourceState) => state.propositions,
104
121
  execute: (proposition, context) => context.run(canonicaliser, proposition),
105
- apply: (state, claims) => ({ ...state, claims }),
122
+ apply: (state, claims) => ({ ...state, claims: [...claims] }),
106
123
  });
107
124
  ```
108
125
 
109
- The executable [collection example](tests/collection-child.ts) includes
126
+ Add `canonicalise` to a pipeline's `nodes` and `routes` like any other participant.
127
+
128
+ The executable [collection test](tests/collection-child.ts) includes
110
129
  conditional recovery, persistence, cancellation and concurrent source runs.
111
130
  Agent visits remain in the source run's native observations and ledger.
112
131
 
@@ -114,3 +133,74 @@ Agent definitions may be reused across collections. Native Tandem binds each und
114
133
  its collection name. Scoped observations and accepted ledger values carry a
115
134
  `visitId` so concurrent calls can be correlated without changing their semantic
116
135
  `stepId`.
136
+
137
+ ## Raw-output agents
138
+
139
+ For a specialised model that requires a plain user message, a raw-output agent
140
+ can set both `instructions` and `output.instructions` to `""`. Tandem then sends
141
+ the authored message without system instructions or a response-format constraint.
142
+ Other agent/output modes still require nonblank instructions.
143
+
144
+ ## Examples
145
+
146
+ The [examples](https://github.com/maxanstey-meridian/tandem-ts/tree/main/examples) run from a
147
+ clone of this repository. The getting-started examples need no model or API key; the songwriter,
148
+ debate and code-writer examples need an OpenRouter API key. See
149
+ [examples/README.md](https://github.com/maxanstey-meridian/tandem-ts/blob/main/examples/README.md)
150
+ for the commands and environment variables.
151
+
152
+ ## Development and publishing
153
+
154
+ This repository is a [pnpm](https://pnpm.io) workspace. `package.json` pins pnpm 11.22.0 in
155
+ `packageManager`; a newer pnpm switches to that version on its own, or run
156
+ `corepack enable pnpm`. Tests also need the .NET 10 runtime.
157
+
158
+ ```sh
159
+ pnpm install
160
+ pnpm build
161
+ pnpm test
162
+ npm pack
163
+ ```
164
+
165
+ `npm pack` and `npm publish` build fresh output before packaging. The npm package contains
166
+ `dist`, including the vendored platform bridge bundles in `dist/runtime`.
167
+
168
+ ### Repository layout
169
+
170
+ - `src`: the published SDK. `runtime/loader.mjs` picks the bridge bundle for the current platform.
171
+ - `runtime/darwin-arm64`, `runtime/linux-x64`: the vendored .NET bridge bundles, copied into
172
+ `dist/runtime` by `pnpm build`.
173
+ - `packets`: `@maxanstey-meridian/tandem-packets`, which reads Markdown packet files with YAML
174
+ frontmatter into a Zod schema. `pnpm test` builds it.
175
+ - `studio`: Tandem Studio, a local Nuxt viewer and route editor for the pipeline exported by a
176
+ `tandem.config.ts`. Run `pnpm -C studio build`, then `node ../../../studio/dist/cli.js` from
177
+ `examples/debate/typescript`. It has its own `typecheck` and `test` scripts.
178
+ - `examples`: see [examples/README.md](examples/README.md).
179
+
180
+ ### Updating the runtime bundles
181
+
182
+ The bundles are built from the [Tandem](https://github.com/maxanstey-meridian/tandem) .NET
183
+ repository; only bundles from a tested Tandem build should be released. Each Tandem release attaches
184
+ `tandem-bridge-<version>-darwin-arm64.tar.gz` and `tandem-bridge-<version>-linux-x64.tar.gz`;
185
+ replace each platform's directory with its archive's contents:
186
+
187
+ ```sh
188
+ rm -rf runtime/darwin-arm64 && mkdir runtime/darwin-arm64
189
+ tar -xzf tandem-bridge-<version>-darwin-arm64.tar.gz -C runtime/darwin-arm64
190
+ ```
191
+
192
+ To build a bundle from a Tandem checkout instead, run this there for each RID (`osx-arm64` for
193
+ `darwin-arm64`, `linux-x64` for `linux-x64`), then replace the matching `runtime/<platform>`
194
+ directory with `bridge-runtime/` (delete it first, so files the new build dropped do not linger):
195
+
196
+ ```sh
197
+ dotnet publish bridge/Tandem.Bridge.csproj -c Release -p:Version=<version> -r osx-arm64 --self-contained false --output .runtime-publish
198
+ node scripts/stage-runtime.mjs osx-arm64
199
+ ```
200
+
201
+ Then run `pnpm test`, which exercises the bundle for the current platform. The Linux package
202
+ workflow tests the linux-x64 bundle.
203
+
204
+ ## License
205
+
206
+ Licensed under the [MIT License](./LICENSE).
package/dist/index.js CHANGED
@@ -94,38 +94,6 @@ class CallbackRegistry {
94
94
  return id;
95
95
  }
96
96
  }
97
- // The bridge surfaces JS callback errors and .NET cancellations only as exception message text,
98
- // so these markers are how a failure's origin is recovered (until the bridge returns structured
99
- // errors).
100
- const CALLBACK_CONTRACT_MARKER = "TANDEM_CALLBACK_CONTRACT:";
101
- const CALLBACK_FAILURE_MARKER = "JavaScript callback failed:";
102
- const BRIDGE_CANCELLATION_MESSAGE = /\boperation was (?:cancell?ed|aborted)\b/i;
103
- const callbackFailureSchema = z.object({
104
- boundary: z.string(),
105
- problems: z.array(z.object({ path: z.string(), message: z.string() })),
106
- });
107
- function callbackContractFailure(error) {
108
- const message = error instanceof Error ? error.message : String(error);
109
- const start = message.indexOf(CALLBACK_CONTRACT_MARKER);
110
- if (start < 0) {
111
- return null;
112
- }
113
- try {
114
- const failure = callbackFailureSchema.safeParse(JSON.parse(message.slice(start + CALLBACK_CONTRACT_MARKER.length)));
115
- return failure.success ? failure.data : null;
116
- }
117
- catch {
118
- return null;
119
- }
120
- }
121
- function isCancellation(error, signal) {
122
- if (signal?.aborted) {
123
- return true;
124
- }
125
- return (error instanceof Error &&
126
- !error.message.includes(CALLBACK_FAILURE_MARKER) &&
127
- (error.name === "AbortError" || BRIDGE_CANCELLATION_MESSAGE.test(error.message)));
128
- }
129
97
  function path(parts) {
130
98
  return parts.length === 0
131
99
  ? "$"
@@ -830,14 +798,28 @@ const acceptedValueFields = z
830
798
  const acceptedValuesSchema = z.array(acceptedValueFields.refine((value) => value.valueType !== null || value.payload !== null, {
831
799
  message: "valueType and payload cannot both be null",
832
800
  }));
833
- const runResultSchema = z
834
- .object({
835
- runId: z.uuid(),
836
- succeeded: z.boolean(),
837
- state: z.unknown(),
838
- summary: z.string().nullable(),
839
- })
840
- .strict();
801
+ const validationProblemsSchema = z.array(z.object({ path: z.string(), message: z.string() }).strict());
802
+ /** How the bridge reports the end of a run; only a bridge defect rejects instead. */
803
+ const runEnvelopeSchema = z.discriminatedUnion("status", [
804
+ z
805
+ .object({
806
+ status: z.enum(["succeeded", "failed"]),
807
+ runId: z.uuid(),
808
+ state: z.unknown(),
809
+ summary: z.string().nullable(),
810
+ })
811
+ .strict(),
812
+ z.object({ status: z.literal("cancelled"), runId: z.uuid() }).strict(),
813
+ z
814
+ .object({
815
+ status: z.literal("contract"),
816
+ runId: z.uuid(),
817
+ boundary: z.string(),
818
+ problems: validationProblemsSchema,
819
+ })
820
+ .strict(),
821
+ z.object({ status: z.literal("faulted"), runId: z.uuid(), message: z.string() }).strict(),
822
+ ]);
841
823
  /** Lazy (and cached by the module loader) so importing the authoring API never starts .NET. */
842
824
  const loadRuntime = () => import("./runtime/loader.mjs");
843
825
  export async function inspectAccepted(options) {
@@ -925,20 +907,28 @@ export async function run(graph, initial, options = {}) {
925
907
  ? result
926
908
  : callbackFailed(new Error(`Internal callback '${id}' is asynchronous.`));
927
909
  }, async (id, state, input, signal) => callbacks.invoke(id, state, input, signal), options.signal);
928
- const result = parseJson(runResultSchema, resultJson, "run result");
929
- return { ...result, state: parse(graph.state, result.state, "final state") };
910
+ const envelope = parseJson(runEnvelopeSchema, resultJson, "run result");
911
+ switch (envelope.status) {
912
+ case "succeeded":
913
+ case "failed":
914
+ return {
915
+ runId: envelope.runId,
916
+ succeeded: envelope.status === "succeeded",
917
+ state: parse(graph.state, envelope.state, "final state"),
918
+ summary: envelope.summary,
919
+ };
920
+ case "cancelled":
921
+ throw new TandemCancellationError(new Error("The run was cancelled."));
922
+ case "contract":
923
+ throw new ContractValidationError(envelope.boundary, envelope.problems);
924
+ case "faulted":
925
+ throw new TandemRuntimeError("run", new Error(envelope.message));
926
+ }
930
927
  }
931
928
  catch (error) {
932
929
  if (error instanceof TandemError) {
933
930
  throw error;
934
931
  }
935
- const callbackFailure = callbackContractFailure(error);
936
- if (callbackFailure) {
937
- throw new ContractValidationError(callbackFailure.boundary, callbackFailure.problems);
938
- }
939
- if (isCancellation(error, options.signal)) {
940
- throw new TandemCancellationError(error);
941
- }
942
932
  throw new TandemRuntimeError("run", error);
943
933
  }
944
934
  finally {
@@ -962,7 +952,6 @@ function issues(schema, input) {
962
952
  throw error;
963
953
  }
964
954
  }
965
- const validationProblemsSchema = z.array(z.object({ path: z.string(), message: z.string() }).strict());
966
955
  function validationProblems(problems, boundary) {
967
956
  return JSON.stringify(parse(validationProblemsSchema, problems, boundary));
968
957
  }
@@ -1,4 +1,4 @@
1
- // Generated for: Tandem.NodeApiSpike.Bridge 0.1.0.0
1
+ // Generated for: Tandem.Bridge 0.3.1.0
2
2
  // Generated by: Microsoft.JavaScript.NodeApi.Generator 0.9.0.0
3
3
  /* eslint-disable */
4
4
 
@@ -1,4 +1,4 @@
1
- // Generated for: Tandem.NodeApiSpike.Bridge 0.1.0.0
1
+ // Generated for: Tandem.Bridge 0.3.1.0
2
2
  // Generated by: Microsoft.JavaScript.NodeApi.Generator 0.9.0.0
3
3
  /* eslint-disable */
4
4
 
@@ -20,7 +20,13 @@ export namespace NodePipelineBridge {
20
20
  runId: string,
21
21
  ): Promise<string>;
22
22
 
23
- /** Registers and runs a complete JavaScript-authored Tandem graph. */
23
+ /**
24
+ * Registers and runs a complete JavaScript-authored Tandem graph and reports how it
25
+ * ended as a {@link Tandem.Bridge.RunEnvelope}. Once a run is attempted every failure is
26
+ * reported, because a fault raised inside the run cannot be told apart from a bridge
27
+ * defect; a fault carries the full exception text. Only a caller without a JavaScript
28
+ * synchronization context gets an exception.
29
+ */
24
30
  export function runRegisteredGraphAsync(
25
31
  definitionJson: string,
26
32
  invokeSyncCallback: Func$4<string, string, string, string>,