@guuey/host 0.1.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/agent-entry.d.ts +38 -0
  4. package/dist/agent-entry.d.ts.map +1 -0
  5. package/dist/agent-entry.js +87 -0
  6. package/dist/boot-context.d.ts +50 -0
  7. package/dist/boot-context.d.ts.map +1 -0
  8. package/dist/boot-context.js +22 -0
  9. package/dist/creds.d.ts +26 -0
  10. package/dist/creds.d.ts.map +1 -0
  11. package/dist/creds.js +45 -0
  12. package/dist/frameworks/claude-options.d.ts +149 -0
  13. package/dist/frameworks/claude-options.d.ts.map +1 -0
  14. package/dist/frameworks/claude-options.js +208 -0
  15. package/dist/frameworks/claude-runner.d.ts +3 -0
  16. package/dist/frameworks/claude-runner.d.ts.map +1 -0
  17. package/dist/frameworks/claude-runner.js +33 -0
  18. package/dist/frameworks/claude.d.ts +72 -0
  19. package/dist/frameworks/claude.d.ts.map +1 -0
  20. package/dist/frameworks/claude.js +80 -0
  21. package/dist/frameworks/google-adk.d.ts +105 -0
  22. package/dist/frameworks/google-adk.d.ts.map +1 -0
  23. package/dist/frameworks/google-adk.js +269 -0
  24. package/dist/frameworks/openai-runner.d.ts +3 -0
  25. package/dist/frameworks/openai-runner.d.ts.map +1 -0
  26. package/dist/frameworks/openai-runner.js +37 -0
  27. package/dist/frameworks/openai.d.ts +65 -0
  28. package/dist/frameworks/openai.d.ts.map +1 -0
  29. package/dist/frameworks/openai.js +207 -0
  30. package/dist/index.d.ts +31 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +134 -0
  33. package/dist/options.d.ts +173 -0
  34. package/dist/options.d.ts.map +1 -0
  35. package/dist/options.js +248 -0
  36. package/dist/preamble.d.ts +16 -0
  37. package/dist/preamble.d.ts.map +1 -0
  38. package/dist/preamble.js +34 -0
  39. package/dist/run-openai.d.ts +65 -0
  40. package/dist/run-openai.d.ts.map +1 -0
  41. package/dist/run-openai.js +207 -0
  42. package/dist/run.d.ts +72 -0
  43. package/dist/run.d.ts.map +1 -0
  44. package/dist/run.js +80 -0
  45. package/dist/sdk-version.d.ts +5 -0
  46. package/dist/sdk-version.d.ts.map +1 -0
  47. package/dist/sdk-version.js +56 -0
  48. package/package.json +78 -0
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The OpenAI (`@openai/agents`) arm of the universal host loop. Mirrors
3
+ * `run.ts`'s `runInvoke` shape: one invoke → an `@openai/agents` `Agent` built
4
+ * from the snapshot, run streamed, each raw `RunStreamEvent` emitted to fd-3 as
5
+ * a `native('openai-agents-sdk', …)` WorkerEvent (the Router dispatches them to
6
+ * the `@silverprotocol/openai-agents` normalizer). On clean completion it emits
7
+ * `done`; on `MaxTurnsExceededError` it emits the `__host_error__` sentinel the
8
+ * normalizer maps to `turn.error` (code `max_turns`), THEN `done(..,"max_turns")`;
9
+ * on any other failure it emits `error`. Never throws — every failure path is a
10
+ * terminal event so the Router always sees one. Emits `hello` FIRST (§8 item B)
11
+ * — the SDK-version handshake.
12
+ *
13
+ * `run` (the `@openai/agents` runner) is injected so the loop is unit-testable
14
+ * without a live model — the entrypoint (`index.ts`) passes the real SDK `run`.
15
+ *
16
+ * Snapshot → Agent mapping:
17
+ * - `model` → `snapshot.model` (or the SDK's own default when absent —
18
+ * NOT Claude's; this is the OpenAI path).
19
+ * - `instructions` → `systemPrompt` + the §1.4 context preamble (reuses
20
+ * `withContextPreamble`, identical to the Claude path).
21
+ * - `mcpServers` → the framework-neutral `resolveMcpServers` output (same
22
+ * federation/env-substitution as Claude), each `http`/`sse`
23
+ * entry → an `MCPServerStreamableHttp` carrying its
24
+ * `Authorization` (and any other) header via `requestInit`.
25
+ * - `maxTurns` → `snapshot.runtime?.maxTurns` (passed to `run(...)`; the SDK
26
+ * THROWS `MaxTurnsExceededError` from `stream.completed`).
27
+ * - API key → `OPENAI_API_KEY` (the Router sets it at pod boot), applied
28
+ * globally via `setDefaultOpenAIKey` by the entrypoint.
29
+ */
30
+ import { Agent, type RunStreamEvent } from "@openai/agents";
31
+ import type { Emitter } from "@guuey/worker";
32
+ import type { HostInvoke, HostRuntime } from "./run.js";
33
+ import { type GuueyAgent } from "@guuey/config";
34
+ /**
35
+ * The streamed-result surface the loop CONSUMES — exactly the three members it
36
+ * reads off the `@openai/agents` `StreamedRunResult`: async iteration over raw
37
+ * events, `completed` (which THROWS `MaxTurnsExceededError`), and the resolved
38
+ * `finalOutput`. A minimal structural interface (NOT the full `StreamedRunResult`
39
+ * class) so the real runner result AND a test fake both satisfy it without a cast
40
+ * — `StreamedRunResult` has private fields, so a plain object could never match
41
+ * the class, but it DOES match this read-only projection.
42
+ */
43
+ export interface OpenaiRunResult extends AsyncIterable<RunStreamEvent> {
44
+ readonly completed: Promise<void>;
45
+ /** Resolved text output for the default text-output Agent (or `undefined`). */
46
+ readonly finalOutput?: string | undefined;
47
+ }
48
+ /**
49
+ * The `run` surface the loop needs — the real `@openai/agents` `run` (streamed
50
+ * overload) satisfies it (its `StreamedRunResult` is assignable to
51
+ * {@link OpenaiRunResult}). Injected so the loop is testable without a live model.
52
+ */
53
+ export type OpenaiRunFn = (agent: Agent, input: string, options: {
54
+ stream: true;
55
+ maxTurns?: number;
56
+ }) => Promise<OpenaiRunResult>;
57
+ /**
58
+ * Run one invoke through `@openai/agents`. Emits `native` per `RunStreamEvent`,
59
+ * `done` on completion (with the `max_turns` sentinel + reason on a turn-cap),
60
+ * `error` on a build failure or any non-max-turns throw. Never throws.
61
+ */
62
+ export declare function runInvokeOpenai(snapshot: GuueyAgent & {
63
+ framework?: string;
64
+ }, invoke: HostInvoke, runtime: HostRuntime, emit: Emitter, run: OpenaiRunFn): Promise<void>;
65
+ //# sourceMappingURL=run-openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-openai.d.ts","sourceRoot":"","sources":["../src/run-openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EACL,KAAK,EAIL,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,OAAO,EAAa,MAAM,eAAe,CAAC;AAOxD,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAA+B,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAQ7E;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAgB,SAAQ,aAAa,CAAC,cAAc,CAAC;IACpE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,KACzC,OAAO,CAAC,eAAe,CAAC,CAAC;AAgB9B;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,UAAU,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7C,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,OAAO,EACb,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,IAAI,CAAC,CA4Gf"}
@@ -0,0 +1,207 @@
1
+ /**
2
+ * The OpenAI (`@openai/agents`) arm of the universal host loop. Mirrors
3
+ * `run.ts`'s `runInvoke` shape: one invoke → an `@openai/agents` `Agent` built
4
+ * from the snapshot, run streamed, each raw `RunStreamEvent` emitted to fd-3 as
5
+ * a `native('openai-agents-sdk', …)` WorkerEvent (the Router dispatches them to
6
+ * the `@silverprotocol/openai-agents` normalizer). On clean completion it emits
7
+ * `done`; on `MaxTurnsExceededError` it emits the `__host_error__` sentinel the
8
+ * normalizer maps to `turn.error` (code `max_turns`), THEN `done(..,"max_turns")`;
9
+ * on any other failure it emits `error`. Never throws — every failure path is a
10
+ * terminal event so the Router always sees one. Emits `hello` FIRST (§8 item B)
11
+ * — the SDK-version handshake.
12
+ *
13
+ * `run` (the `@openai/agents` runner) is injected so the loop is unit-testable
14
+ * without a live model — the entrypoint (`index.ts`) passes the real SDK `run`.
15
+ *
16
+ * Snapshot → Agent mapping:
17
+ * - `model` → `snapshot.model` (or the SDK's own default when absent —
18
+ * NOT Claude's; this is the OpenAI path).
19
+ * - `instructions` → `systemPrompt` + the §1.4 context preamble (reuses
20
+ * `withContextPreamble`, identical to the Claude path).
21
+ * - `mcpServers` → the framework-neutral `resolveMcpServers` output (same
22
+ * federation/env-substitution as Claude), each `http`/`sse`
23
+ * entry → an `MCPServerStreamableHttp` carrying its
24
+ * `Authorization` (and any other) header via `requestInit`.
25
+ * - `maxTurns` → `snapshot.runtime?.maxTurns` (passed to `run(...)`; the SDK
26
+ * THROWS `MaxTurnsExceededError` from `stream.completed`).
27
+ * - API key → `OPENAI_API_KEY` (the Router sets it at pod boot), applied
28
+ * globally via `setDefaultOpenAIKey` by the entrypoint.
29
+ */
30
+ import { Agent, MaxTurnsExceededError, MCPServerStreamableHttp, } from "@openai/agents";
31
+ import { resolveMcpServers, withContextPreamble, } from "./options.js";
32
+ import { GUUEY_DEFAULT_SYSTEM_PROMPT } from "@guuey/config";
33
+ import { resolveSdkVersion } from "./sdk-version.js";
34
+ /** The framework tag this arm runs — matches the `AgentFramework` enum value. */
35
+ const OPENAI_FRAMEWORK = "openai-agents-sdk";
36
+ /** The npm package whose installed version is this framework's `hello.sdkVersion`. */
37
+ const OPENAI_SDK_PACKAGE = "@openai/agents";
38
+ /**
39
+ * Run one invoke through `@openai/agents`. Emits `native` per `RunStreamEvent`,
40
+ * `done` on completion (with the `max_turns` sentinel + reason on a turn-cap),
41
+ * `error` on a build failure or any non-max-turns throw. Never throws.
42
+ */
43
+ export async function runInvokeOpenai(snapshot, invoke, runtime, emit, run) {
44
+ // The SDK-version handshake (§8 item B, additive-optional) — ALWAYS the
45
+ // first event of this invoke's fd-3 stream, before any native/turn event.
46
+ // `sdkVersion` is resolved at runtime, never hardcoded; `null` (SDK not
47
+ // resolvable) is tolerated by the Router.
48
+ emit.hello(OPENAI_FRAMEWORK, OPENAI_SDK_PACKAGE, resolveSdkVersion(OPENAI_SDK_PACKAGE));
49
+ // The host runs inside the Router's bubblewrap jail with NO IRSA; a federated
50
+ // MCP server reads its Router-written credential file. `resolveMcpServers`
51
+ // needs the same per-invoke context the Claude path builds.
52
+ const ctx = {
53
+ input: invoke.input,
54
+ identity: invoke.identity,
55
+ fs: invoke.fs,
56
+ history: invoke.history,
57
+ listCredentials: runtime.listCredentials,
58
+ ...(runtime.apiKey !== undefined ? { apiKey: runtime.apiKey } : {}),
59
+ ...(invoke.priorMemory !== undefined ? { priorMemory: invoke.priorMemory } : {}),
60
+ ...(invoke.priorState !== undefined ? { priorState: invoke.priorState } : {}),
61
+ };
62
+ // Build the Agent (+ connect its MCP servers). A build failure (e.g. an
63
+ // unresolved {file} system prompt, or an unsupported MCP arm) is a terminal
64
+ // `error` — never a throw out of this function.
65
+ let instructions;
66
+ let mcpServers;
67
+ try {
68
+ // Snapshots reach the worker fully resolved — the CLI inlines any `{file}`
69
+ // system prompt before upload. A non-string reaching the worker means a
70
+ // direct API hit with an un-resolved snapshot; reject loudly (same as Claude).
71
+ if (snapshot.systemPrompt !== undefined && typeof snapshot.systemPrompt !== "string") {
72
+ throw new Error(`@guuey/host: snapshot.systemPrompt must be a resolved string (got ${JSON.stringify(snapshot.systemPrompt)}). The CLI inlines {file} references before upload; workers never read the filesystem.`);
73
+ }
74
+ instructions = withContextPreamble(snapshot.systemPrompt ?? GUUEY_DEFAULT_SYSTEM_PROMPT, ctx.history, ctx.priorMemory, ctx.priorState);
75
+ mcpServers = buildOpenaiMcpServers(ctx);
76
+ }
77
+ catch (err) {
78
+ emit.error(err instanceof Error ? err.message : String(err));
79
+ return;
80
+ }
81
+ try {
82
+ for (const server of mcpServers) {
83
+ await server.connect();
84
+ }
85
+ }
86
+ catch (err) {
87
+ await closeAll(mcpServers);
88
+ emit.error(err instanceof Error ? err.message : String(err));
89
+ return;
90
+ }
91
+ try {
92
+ const maxTurns = snapshot.runtime?.maxTurns;
93
+ // `MCPServerStreamableHttp` IS an `MCPServer`; the Agent's `mcpServers` field
94
+ // is `MCPServer[]` — widen via the interface (no cast; structural subtype).
95
+ const servers = mcpServers;
96
+ const agent = new Agent({
97
+ name: "guuey-agent",
98
+ instructions,
99
+ mcpServers: servers,
100
+ ...(snapshot.model !== undefined ? { model: snapshot.model } : {}),
101
+ });
102
+ const stream = await run(agent, invoke.input, {
103
+ stream: true,
104
+ ...(maxTurns !== undefined ? { maxTurns } : {}),
105
+ });
106
+ // Drive the stream: each raw event crosses the wire as native.
107
+ for await (const ev of stream) {
108
+ emit.native(OPENAI_FRAMEWORK, toJson(ev));
109
+ }
110
+ // The runner THROWS `MaxTurnsExceededError` from `completed` AFTER the stream
111
+ // ends. Catch it specifically → feed the normalizer the `__host_error__`
112
+ // sentinel, then a terminal `done(.., "max_turns")`. Other throws → `error`.
113
+ try {
114
+ await stream.completed;
115
+ }
116
+ catch (err) {
117
+ if (err instanceof MaxTurnsExceededError) {
118
+ // The `__host_error__` sentinel — a fresh object literal so it is checked
119
+ // directly against `JsonValue` (the `native` payload type). Shape is the
120
+ // BINDING contract {@link HostErrorSentinel}.
121
+ emit.native(OPENAI_FRAMEWORK, {
122
+ type: "__host_error__",
123
+ code: "max_turns",
124
+ message: err.message,
125
+ });
126
+ emit.done(finalText(stream), "max_turns");
127
+ return;
128
+ }
129
+ throw err;
130
+ }
131
+ emit.done(finalText(stream), "end_turn");
132
+ }
133
+ catch (err) {
134
+ emit.error(err instanceof Error ? err.message : String(err));
135
+ }
136
+ finally {
137
+ await closeAll(mcpServers);
138
+ }
139
+ }
140
+ /**
141
+ * The agent's final text output, or `""` when absent (a tool-only turn, or a
142
+ * turn cut short by `max_turns`). For the default text-output Agent `finalOutput`
143
+ * is `string | undefined`.
144
+ */
145
+ function finalText(stream) {
146
+ const out = stream.finalOutput;
147
+ return typeof out === "string" ? out : "";
148
+ }
149
+ /**
150
+ * Translate the framework-neutral resolved MCP map → connected-capable
151
+ * `MCPServerStreamableHttp` instances. Each `http`/`sse` entry's `headers`
152
+ * (typically `{ authorization: 'Bearer <token>' }`, from the federation
153
+ * credential file or `${env.NAME}` substitution) ride on the underlying MCP
154
+ * transport's `RequestInit.headers` via `requestInit`.
155
+ *
156
+ * The cred dir only yields `http`/`sse` servers (the Router resolves all
157
+ * transport to one of those two); the `stdio` arm below is unreachable
158
+ * defensive code — kept so a future schema change can't silently drop a server.
159
+ */
160
+ function buildOpenaiMcpServers(ctx) {
161
+ const resolved = resolveMcpServers(ctx);
162
+ const servers = [];
163
+ for (const [name, entry] of Object.entries(resolved)) {
164
+ servers.push(toOpenaiMcpServer(name, entry));
165
+ }
166
+ return servers;
167
+ }
168
+ /**
169
+ * One resolved `SdkMcpServer` → an `MCPServerStreamableHttp`. Both `http` and
170
+ * `sse` arms map to the StreamableHTTP server (the SDK's HTTP MCP transport);
171
+ * the `stdio` arm is unreachable (`resolveMcpServers` rejects colocated before
172
+ * we get here) — handled with a loud throw so a future schema change can't
173
+ * silently drop a server.
174
+ */
175
+ function toOpenaiMcpServer(name, entry) {
176
+ if (entry.type === "stdio") {
177
+ throw new Error(`mcpServers["${name}"]: stdio (colocated) MCP is not supported on the OpenAI host path.`);
178
+ }
179
+ const headers = entry.headers;
180
+ return new MCPServerStreamableHttp({
181
+ url: entry.url,
182
+ name,
183
+ ...(headers && Object.keys(headers).length > 0 ? { requestInit: { headers } } : {}),
184
+ // `customDataExtractor` (agents 0.12+) is the ONLY channel that carries an
185
+ // MCP tool result's `structuredContent` onto the wire (`item.customData`)
186
+ // WITHOUT leaking it into model-visible text — without this, the ggui cache
187
+ // marker never reaches the normalizer and render metering goes blind.
188
+ // Mirrors the verified silverprotocol capture-agent wiring; the facet reads
189
+ // `item.customData.structuredContent`.
190
+ customDataExtractor: (context) => context.structuredContent !== undefined
191
+ ? { structuredContent: context.structuredContent }
192
+ : undefined,
193
+ });
194
+ }
195
+ /** Best-effort close of every connected MCP server (release the transport). */
196
+ async function closeAll(servers) {
197
+ await Promise.allSettled(servers.map((s) => s.close()));
198
+ }
199
+ /**
200
+ * Coerce one `RunStreamEvent` to the `JsonValue` the `native` event carries.
201
+ * Stream events are plain JSON-serializable objects; the round-trip drops any
202
+ * non-JSON surface and yields the exact shape the Router's normalizer parses off
203
+ * the wire. Mirrors `run.ts`'s `toJson`.
204
+ */
205
+ function toJson(ev) {
206
+ return JSON.parse(JSON.stringify(ev));
207
+ }
package/dist/run.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The testable core of the worker loop. `runInvoke` runs one invoke through the
3
+ * Claude Agent SDK and emits each native `SDKMessage` to fd-3 as a `native`
4
+ * WorkerEvent (the Router dispatches them to the matching normalizer). On the
5
+ * SDK result message it emits `done`; on a throw or a framework-gate violation
6
+ * it emits `error`. Emits `hello` FIRST (§8 item B) — the SDK-version handshake.
7
+ *
8
+ * `query` is injected so the loop is unit-testable without a live model. The
9
+ * worker entrypoint (`index.ts`) passes the real `@anthropic-ai/claude-agent-sdk`
10
+ * `query`.
11
+ */
12
+ import type { Options, SDKMessage } from "@anthropic-ai/claude-agent-sdk";
13
+ import type { Emitter, Fs, HistoryMessage, Identity, JsonValue } from "@guuey/worker";
14
+ import { buildOptions, type CredentialFile, type PriorMemoryRecord } from "./options.js";
15
+ /**
16
+ * The invoke this host consumes. A superset of `@guuey/worker`'s `Invoke`:
17
+ * `priorMemory`/`priorState` are the §1.4 push-by-value context the worker reads
18
+ * for the preamble (the Worker Protocol `Invoke` is EXTENDED with these in
19
+ * Task 3; until then the worker loop parses them off the raw control line).
20
+ */
21
+ export interface HostInvoke {
22
+ input: string;
23
+ identity: Identity;
24
+ fs: Fs;
25
+ history: HistoryMessage[];
26
+ priorMemory?: PriorMemoryRecord[];
27
+ priorState?: JsonValue;
28
+ }
29
+ /** Per-process config the worker resolves once at boot. */
30
+ export interface HostRuntime {
31
+ /**
32
+ * Anthropic API key — local-dev fallback when `baseUrl`+`authToken` are
33
+ * absent. One of (`baseUrl`+`authToken`) or `apiKey` must be provided;
34
+ * `buildOptions` throws at invoke time if neither is present.
35
+ */
36
+ apiKey?: string;
37
+ /**
38
+ * Loopback proxy base URL (hosted/broker mode). Task 8 injects this as
39
+ * `ANTHROPIC_BASE_URL` via `buildWorkerEnv`. When set together with
40
+ * `authToken`, the Claude CLI subprocess routes through the managed-LLM
41
+ * broker; the real API key is intentionally absent to prevent leaks.
42
+ */
43
+ baseUrl?: string;
44
+ /**
45
+ * Opaque session token for the loopback proxy (hosted/broker mode). Task 8
46
+ * injects this as `ANTHROPIC_AUTH_TOKEN`. Required when `baseUrl` is set.
47
+ */
48
+ authToken?: string;
49
+ /**
50
+ * Returns every credential the Router broker wrote to
51
+ * `<sessionDir>/.guuey/credentials/` this invoke. Injected so the run path
52
+ * stays pure (no disk access inside `runInvoke`).
53
+ */
54
+ listCredentials: () => Array<{
55
+ name: string;
56
+ cred: CredentialFile;
57
+ }>;
58
+ }
59
+ /** The `query` surface the loop needs — the real SDK `query` satisfies it. */
60
+ export type QueryFn = (params: {
61
+ prompt: string;
62
+ options: Options;
63
+ }) => AsyncIterable<SDKMessage>;
64
+ /**
65
+ * Run one invoke. Emits `native` per SDKMessage, `done` on the result, `error`
66
+ * on a framework-gate violation or a thrown error. Never throws — every failure
67
+ * path becomes an `error` event so the Router always sees a terminal event.
68
+ */
69
+ export declare function runInvoke(snapshot: {
70
+ framework?: string;
71
+ } & Parameters<typeof buildOptions>[0], invoke: HostInvoke, runtime: HostRuntime, emit: Emitter, query: QueryFn): Promise<void>;
72
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAc,MAAM,eAAe,CAAC;AAClG,OAAO,EACL,YAAY,EAEZ,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAC;AAQtB;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,EAAE,EAAE,EAAE,CAAC;IACP,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,2DAA2D;AAC3D,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,EAAE,MAAM,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,cAAc,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,8EAA8E;AAC9E,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,KAAK,aAAa,CAAC,UAAU,CAAC,CAAC;AA0BhC;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACrE,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,IAAI,CAAC,CAgDf"}
package/dist/run.js ADDED
@@ -0,0 +1,80 @@
1
+ import { buildOptions, } from "./options.js";
2
+ import { resolveSdkVersion } from "./sdk-version.js";
3
+ /** The framework THIS run path runs. OpenAI has its own path (`run-openai.ts`). */
4
+ const CLAUDE_FRAMEWORK = "claude-agent-sdk";
5
+ /** The npm package whose installed version is this framework's `hello.sdkVersion`. */
6
+ const CLAUDE_SDK_PACKAGE = "@anthropic-ai/claude-agent-sdk";
7
+ function isResultMessage(msg) {
8
+ return msg.type === "result";
9
+ }
10
+ /** Map the SDK result message → the Worker Protocol `done` stopReason + result. */
11
+ function resultToDone(msg) {
12
+ const result = typeof msg.result === "string" ? msg.result : "";
13
+ const stopReason = msg.subtype === "success"
14
+ ? "end_turn"
15
+ : msg.subtype === "error_max_turns"
16
+ ? "max_turns"
17
+ : "error";
18
+ return { stopReason, result };
19
+ }
20
+ /**
21
+ * Run one invoke. Emits `native` per SDKMessage, `done` on the result, `error`
22
+ * on a framework-gate violation or a thrown error. Never throws — every failure
23
+ * path becomes an `error` event so the Router always sees a terminal event.
24
+ */
25
+ export async function runInvoke(snapshot, invoke, runtime, emit, query) {
26
+ // The SDK-version handshake (§8 item B, additive-optional) — ALWAYS the
27
+ // first event of this invoke's fd-3 stream, before the framework gate or any
28
+ // native/turn event. `sdkVersion` is resolved at runtime, never hardcoded;
29
+ // `null` (SDK not resolvable) is tolerated by the Router.
30
+ emit.hello(CLAUDE_FRAMEWORK, CLAUDE_SDK_PACKAGE, resolveSdkVersion(CLAUDE_SDK_PACKAGE));
31
+ // Framework gate: this run path is the Claude SDK. OpenAI agents route to
32
+ // `runInvokeOpenai` (selected in `index.ts` by `snapshot.framework`) and never
33
+ // reach here. A non-claude framework arriving here (e.g. `google-adk`,
34
+ // `vanilla`) has no run path yet → a clear `error`, never a silent mis-run.
35
+ if (snapshot.framework && snapshot.framework !== CLAUDE_FRAMEWORK) {
36
+ emit.error(`@guuey/host: the claude run path got framework '${snapshot.framework}'; no run path for it yet.`);
37
+ return;
38
+ }
39
+ let options;
40
+ try {
41
+ const ctx = {
42
+ input: invoke.input,
43
+ identity: invoke.identity,
44
+ fs: invoke.fs,
45
+ history: invoke.history,
46
+ listCredentials: runtime.listCredentials,
47
+ ...(runtime.apiKey !== undefined ? { apiKey: runtime.apiKey } : {}),
48
+ ...(runtime.baseUrl !== undefined ? { baseUrl: runtime.baseUrl } : {}),
49
+ ...(runtime.authToken !== undefined ? { authToken: runtime.authToken } : {}),
50
+ ...(invoke.priorMemory !== undefined ? { priorMemory: invoke.priorMemory } : {}),
51
+ ...(invoke.priorState !== undefined ? { priorState: invoke.priorState } : {}),
52
+ };
53
+ options = buildOptions(snapshot, ctx);
54
+ }
55
+ catch (err) {
56
+ emit.error(err instanceof Error ? err.message : String(err));
57
+ return;
58
+ }
59
+ try {
60
+ let done;
61
+ for await (const msg of query({ prompt: invoke.input, options })) {
62
+ emit.native(CLAUDE_FRAMEWORK, toJson(msg));
63
+ if (isResultMessage(msg))
64
+ done = resultToDone(msg);
65
+ }
66
+ emit.done(done?.result ?? "", done?.stopReason ?? "end_turn");
67
+ }
68
+ catch (err) {
69
+ emit.error(err instanceof Error ? err.message : String(err));
70
+ }
71
+ }
72
+ /**
73
+ * Coerce one SDKMessage to the `JsonValue` the `native` event carries. SDK
74
+ * messages are plain JSON-serializable objects; the round-trip drops any
75
+ * non-JSON surface (functions/symbols never appear on them) and yields the
76
+ * exact shape the Router's normalizer parses off the wire.
77
+ */
78
+ function toJson(msg) {
79
+ return JSON.parse(JSON.stringify(msg));
80
+ }
@@ -0,0 +1,5 @@
1
+ /** Reads the installed `pkgName` package's own `package.json#version`, or
2
+ * `null` when the package isn't installed / its version can't be resolved
3
+ * (never throws — a missing SDK is tolerated, not fatal). */
4
+ export declare function resolveSdkVersion(pkgName: string, anchorPath?: string): string | null;
5
+ //# sourceMappingURL=sdk-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-version.d.ts","sourceRoot":"","sources":["../src/sdk-version.ts"],"names":[],"mappings":"AA0BA;;8DAE8D;AAC9D,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6BrF"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Resolves an installed SDK package's own `package.json#version` at RUNTIME —
3
+ * the real resolved version (never a hardcoded literal, and never the
4
+ * `package.json` dependency RANGE, which may be a caret). Feeds the worker
5
+ * hello handshake's `sdkVersion` field (§8 item B).
6
+ *
7
+ * Mirrors silverprotocol's `resolveSdkVersion`
8
+ * (`sdks/typescript/packages/e2e/src/capture-cli.ts`): `require.resolve(pkg)`
9
+ * finds the package's main entry — `require.resolve(`${pkg}/package.json`)`
10
+ * (the naive approach) THROWS `ERR_PACKAGE_PATH_NOT_EXPORTED` for a package
11
+ * whose `exports` map omits a `./package.json` subpath, which is the case for
12
+ * BOTH `@anthropic-ai/claude-agent-sdk` and `@openai/agents` (verified
13
+ * empirically in that playbook run) — so we walk up from the main entry's
14
+ * directory instead, bounded to a few levels (real packages are 0-2 levels
15
+ * deep), until we find the `package.json` whose own `name` matches.
16
+ */
17
+ import { createRequire } from "node:module";
18
+ import { pathToFileURL } from "node:url";
19
+ import { readFileSync } from "node:fs";
20
+ import { dirname, join } from "node:path";
21
+ const require = createRequire(import.meta.url);
22
+ /** Real packages are 0-2 levels deep from their resolved main entry. */
23
+ const MAX_WALK_UP_DEPTH = 5;
24
+ /** Reads the installed `pkgName` package's own `package.json#version`, or
25
+ * `null` when the package isn't installed / its version can't be resolved
26
+ * (never throws — a missing SDK is tolerated, not fatal). */
27
+ export function resolveSdkVersion(pkgName, anchorPath) {
28
+ try {
29
+ // `anchorPath` re-anchors resolution to another tree (the graceful agent
30
+ // entry's node_modules) — the single-copy rule needs the version of the
31
+ // copy actually driven, not the host's.
32
+ const resolver = anchorPath !== undefined ? createRequire(pathToFileURL(anchorPath).href) : require;
33
+ const mainEntryPath = resolver.resolve(pkgName);
34
+ let dir = dirname(mainEntryPath);
35
+ for (let depth = 0; depth < MAX_WALK_UP_DEPTH; depth++) {
36
+ const candidate = join(dir, "package.json");
37
+ try {
38
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
39
+ if (parsed.name === pkgName) {
40
+ return parsed.version ?? null;
41
+ }
42
+ }
43
+ catch {
44
+ // Not found at this level (or unparsable) — keep walking up.
45
+ }
46
+ const parent = dirname(dir);
47
+ if (parent === dir)
48
+ break; // reached filesystem root
49
+ dir = parent;
50
+ }
51
+ return null;
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@guuey/host",
3
+ "version": "0.1.0",
4
+ "description": "The universal config-driven Guuey worker. Reads the resolved agent.json snapshot, runs the Claude Agent SDK, and emits each native SDKMessage to fd-3 as a `native` WorkerEvent.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "guuey-host": "dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "LICENSE",
15
+ "README.md"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
24
+ "dependencies": {
25
+ "@guuey/config": "0.1.1",
26
+ "@guuey/worker": "0.1.1"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^24.0.0",
30
+ "typescript": "^5.0.0",
31
+ "vitest": "^3.0.0",
32
+ "@anthropic-ai/claude-agent-sdk": "^0.3.199",
33
+ "@openai/agents": "^0.12.0",
34
+ "@google/adk": "^1.3.0"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "keywords": [
40
+ "guuey",
41
+ "agent",
42
+ "worker",
43
+ "host",
44
+ "claude-agent-sdk"
45
+ ],
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/withguuey/guuey-sdks.git",
49
+ "directory": "packages/host"
50
+ },
51
+ "homepage": "https://guuey.com",
52
+ "bugs": {
53
+ "url": "https://github.com/loqu-co/guuey/issues"
54
+ },
55
+ "peerDependencies": {
56
+ "@anthropic-ai/claude-agent-sdk": "^0.3.199",
57
+ "@google/adk": ">=1.0.0 <2",
58
+ "@openai/agents": "^0.12.0"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "@anthropic-ai/claude-agent-sdk": {
62
+ "optional": true
63
+ },
64
+ "@google/adk": {
65
+ "optional": true
66
+ },
67
+ "@openai/agents": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "scripts": {
72
+ "build": "tsc -p tsconfig.build.json",
73
+ "dev": "tsc --watch",
74
+ "typecheck": "tsc --noEmit",
75
+ "test": "vitest run",
76
+ "test:watch": "vitest"
77
+ }
78
+ }