@finalai/agent-provider 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.

Potentially problematic release.


This version of @finalai/agent-provider might be problematic. Click here for more details.

Files changed (176) hide show
  1. package/README.md +12 -0
  2. package/dist/modules/channel-context.d.mts +102 -0
  3. package/dist/modules/channel-context.mjs +52 -0
  4. package/dist/modules/channel-context.mjs.map +7 -0
  5. package/dist/modules/channel-socket.d.mts +65 -0
  6. package/dist/modules/channel-socket.mjs +188 -0
  7. package/dist/modules/channel-socket.mjs.map +7 -0
  8. package/dist/modules/channel.d.mts +382 -0
  9. package/dist/modules/channel.mjs +146 -0
  10. package/dist/modules/channel.mjs.map +7 -0
  11. package/dist/modules/conformance/agent.d.mts +20 -0
  12. package/dist/modules/conformance/agent.mjs +623 -0
  13. package/dist/modules/conformance/agent.mjs.map +7 -0
  14. package/dist/modules/conformance/fixtures.d.mts +41 -0
  15. package/dist/modules/conformance/fixtures.mjs +190 -0
  16. package/dist/modules/conformance/fixtures.mjs.map +7 -0
  17. package/dist/modules/conformance/harness-kit.d.mts +82 -0
  18. package/dist/modules/conformance/harness-kit.mjs +1303 -0
  19. package/dist/modules/conformance/harness-kit.mjs.map +7 -0
  20. package/dist/modules/conformance/image.d.mts +2 -0
  21. package/dist/modules/conformance/image.mjs +79 -0
  22. package/dist/modules/conformance/image.mjs.map +7 -0
  23. package/dist/modules/conformance/index.d.mts +8 -0
  24. package/dist/modules/conformance/index.mjs +12 -0
  25. package/dist/modules/conformance/index.mjs.map +7 -0
  26. package/dist/modules/conformance/local-harness.d.mts +25 -0
  27. package/dist/modules/conformance/local-harness.mjs +108 -0
  28. package/dist/modules/conformance/local-harness.mjs.map +7 -0
  29. package/dist/modules/conformance/protocol.d.mts +30 -0
  30. package/dist/modules/conformance/protocol.mjs +56 -0
  31. package/dist/modules/conformance/protocol.mjs.map +7 -0
  32. package/dist/modules/conformance/sandbox-kit.d.mts +19 -0
  33. package/dist/modules/conformance/sandbox-kit.mjs +320 -0
  34. package/dist/modules/conformance/sandbox-kit.mjs.map +7 -0
  35. package/dist/modules/conformance/scripted-model/chat-completions.d.mts +30 -0
  36. package/dist/modules/conformance/scripted-model/chat-completions.mjs +125 -0
  37. package/dist/modules/conformance/scripted-model/chat-completions.mjs.map +7 -0
  38. package/dist/modules/conformance/scripted-model/directives.d.mts +84 -0
  39. package/dist/modules/conformance/scripted-model/directives.mjs +304 -0
  40. package/dist/modules/conformance/scripted-model/directives.mjs.map +7 -0
  41. package/dist/modules/conformance/scripted-model/handler.d.mts +14 -0
  42. package/dist/modules/conformance/scripted-model/handler.mjs +133 -0
  43. package/dist/modules/conformance/scripted-model/handler.mjs.map +7 -0
  44. package/dist/modules/conformance/scripted-model/index.d.mts +6 -0
  45. package/dist/modules/conformance/scripted-model/index.mjs +7 -0
  46. package/dist/modules/conformance/scripted-model/index.mjs.map +7 -0
  47. package/dist/modules/conformance/scripted-model/messages.d.mts +39 -0
  48. package/dist/modules/conformance/scripted-model/messages.mjs +139 -0
  49. package/dist/modules/conformance/scripted-model/messages.mjs.map +7 -0
  50. package/dist/modules/conformance/scripted-model/responses.d.mts +62 -0
  51. package/dist/modules/conformance/scripted-model/responses.mjs +273 -0
  52. package/dist/modules/conformance/scripted-model/responses.mjs.map +7 -0
  53. package/dist/modules/conformance/scripted-model/sse.d.mts +15 -0
  54. package/dist/modules/conformance/scripted-model/sse.mjs +75 -0
  55. package/dist/modules/conformance/scripted-model/sse.mjs.map +7 -0
  56. package/dist/modules/conformance/v1-agent.d.mts +24 -0
  57. package/dist/modules/conformance/v1-agent.mjs +40 -0
  58. package/dist/modules/conformance/v1-agent.mjs.map +7 -0
  59. package/dist/modules/credential-routes.d.mts +24 -0
  60. package/dist/modules/credential-routes.mjs +25 -0
  61. package/dist/modules/credential-routes.mjs.map +7 -0
  62. package/dist/modules/credentials.d.mts +206 -0
  63. package/dist/modules/credentials.mjs +207 -0
  64. package/dist/modules/credentials.mjs.map +7 -0
  65. package/dist/modules/dynamic.d.mts +32 -0
  66. package/dist/modules/dynamic.mjs +77 -0
  67. package/dist/modules/dynamic.mjs.map +7 -0
  68. package/dist/modules/harness.d.mts +397 -0
  69. package/dist/modules/harness.mjs +213 -0
  70. package/dist/modules/harness.mjs.map +7 -0
  71. package/dist/modules/index.d.mts +15 -0
  72. package/dist/modules/index.mjs +40 -0
  73. package/dist/modules/index.mjs.map +7 -0
  74. package/dist/modules/json.d.mts +12 -0
  75. package/dist/modules/json.mjs +19 -0
  76. package/dist/modules/json.mjs.map +7 -0
  77. package/dist/modules/local-channel-socket.d.mts +23 -0
  78. package/dist/modules/local-channel-socket.mjs +141 -0
  79. package/dist/modules/local-channel-socket.mjs.map +7 -0
  80. package/dist/modules/manifest.d.mts +558 -0
  81. package/dist/modules/manifest.mjs +266 -0
  82. package/dist/modules/manifest.mjs.map +7 -0
  83. package/dist/modules/mcp-content.d.mts +21 -0
  84. package/dist/modules/mcp-content.mjs +86 -0
  85. package/dist/modules/mcp-content.mjs.map +7 -0
  86. package/dist/modules/provider-ref.d.mts +29 -0
  87. package/dist/modules/provider-ref.mjs +55 -0
  88. package/dist/modules/provider-ref.mjs.map +7 -0
  89. package/dist/modules/sandbox/bytes.d.mts +9 -0
  90. package/dist/modules/sandbox/bytes.mjs +154 -0
  91. package/dist/modules/sandbox/bytes.mjs.map +7 -0
  92. package/dist/modules/sandbox/client.d.mts +51 -0
  93. package/dist/modules/sandbox/client.mjs +500 -0
  94. package/dist/modules/sandbox/client.mjs.map +7 -0
  95. package/dist/modules/sandbox/confined-files.d.mts +22 -0
  96. package/dist/modules/sandbox/confined-files.mjs +142 -0
  97. package/dist/modules/sandbox/confined-files.mjs.map +7 -0
  98. package/dist/modules/sandbox/convenience.d.mts +10 -0
  99. package/dist/modules/sandbox/convenience.mjs +116 -0
  100. package/dist/modules/sandbox/convenience.mjs.map +7 -0
  101. package/dist/modules/sandbox/editor.d.mts +49 -0
  102. package/dist/modules/sandbox/editor.mjs +43 -0
  103. package/dist/modules/sandbox/editor.mjs.map +7 -0
  104. package/dist/modules/sandbox/frame-channel.d.mts +47 -0
  105. package/dist/modules/sandbox/frame-channel.mjs +153 -0
  106. package/dist/modules/sandbox/frame-channel.mjs.map +7 -0
  107. package/dist/modules/sandbox/index.d.mts +18 -0
  108. package/dist/modules/sandbox/index.mjs +70 -0
  109. package/dist/modules/sandbox/index.mjs.map +7 -0
  110. package/dist/modules/sandbox/mcp-channel.d.mts +9 -0
  111. package/dist/modules/sandbox/mcp-channel.mjs +77 -0
  112. package/dist/modules/sandbox/mcp-channel.mjs.map +7 -0
  113. package/dist/modules/sandbox/mcp-hosted.d.mts +46 -0
  114. package/dist/modules/sandbox/mcp-hosted.mjs +221 -0
  115. package/dist/modules/sandbox/mcp-hosted.mjs.map +7 -0
  116. package/dist/modules/sandbox/mcp-search.d.mts +51 -0
  117. package/dist/modules/sandbox/mcp-search.mjs +129 -0
  118. package/dist/modules/sandbox/mcp-search.mjs.map +7 -0
  119. package/dist/modules/sandbox/mcp-shell.d.mts +21 -0
  120. package/dist/modules/sandbox/mcp-shell.mjs +205 -0
  121. package/dist/modules/sandbox/mcp-shell.mjs.map +7 -0
  122. package/dist/modules/sandbox/mcp-testing.d.mts +11 -0
  123. package/dist/modules/sandbox/mcp-testing.mjs +33 -0
  124. package/dist/modules/sandbox/mcp-testing.mjs.map +7 -0
  125. package/dist/modules/sandbox/mcp.d.mts +83 -0
  126. package/dist/modules/sandbox/mcp.mjs +971 -0
  127. package/dist/modules/sandbox/mcp.mjs.map +7 -0
  128. package/dist/modules/sandbox/memory-sandbox.d.mts +32 -0
  129. package/dist/modules/sandbox/memory-sandbox.mjs +416 -0
  130. package/dist/modules/sandbox/memory-sandbox.mjs.map +7 -0
  131. package/dist/modules/sandbox/memory-shell.d.mts +29 -0
  132. package/dist/modules/sandbox/memory-shell.mjs +278 -0
  133. package/dist/modules/sandbox/memory-shell.mjs.map +7 -0
  134. package/dist/modules/sandbox/open-scope-node.d.mts +4 -0
  135. package/dist/modules/sandbox/open-scope-node.mjs +19 -0
  136. package/dist/modules/sandbox/open-scope-node.mjs.map +7 -0
  137. package/dist/modules/sandbox/open-scope.d.mts +16 -0
  138. package/dist/modules/sandbox/open-scope.mjs +66 -0
  139. package/dist/modules/sandbox/open-scope.mjs.map +7 -0
  140. package/dist/modules/sandbox/paths.d.mts +7 -0
  141. package/dist/modules/sandbox/paths.mjs +50 -0
  142. package/dist/modules/sandbox/paths.mjs.map +7 -0
  143. package/dist/modules/sandbox/rpc-peer.d.mts +35 -0
  144. package/dist/modules/sandbox/rpc-peer.mjs +224 -0
  145. package/dist/modules/sandbox/rpc-peer.mjs.map +7 -0
  146. package/dist/modules/sandbox/seeds.d.mts +26 -0
  147. package/dist/modules/sandbox/seeds.mjs +228 -0
  148. package/dist/modules/sandbox/seeds.mjs.map +7 -0
  149. package/dist/modules/sandbox/server.d.mts +66 -0
  150. package/dist/modules/sandbox/server.mjs +567 -0
  151. package/dist/modules/sandbox/server.mjs.map +7 -0
  152. package/dist/modules/sandbox/snapshots.d.mts +49 -0
  153. package/dist/modules/sandbox/snapshots.mjs +55 -0
  154. package/dist/modules/sandbox/snapshots.mjs.map +7 -0
  155. package/dist/modules/sandbox/stream-mux.d.mts +18 -0
  156. package/dist/modules/sandbox/stream-mux.mjs +285 -0
  157. package/dist/modules/sandbox/stream-mux.mjs.map +7 -0
  158. package/dist/modules/sandbox/testing.d.mts +25 -0
  159. package/dist/modules/sandbox/testing.mjs +64 -0
  160. package/dist/modules/sandbox/testing.mjs.map +7 -0
  161. package/dist/modules/sandbox/types.d.mts +352 -0
  162. package/dist/modules/sandbox/types.mjs +86 -0
  163. package/dist/modules/sandbox/types.mjs.map +7 -0
  164. package/dist/modules/sandbox/wire.d.mts +671 -0
  165. package/dist/modules/sandbox/wire.mjs +537 -0
  166. package/dist/modules/sandbox/wire.mjs.map +7 -0
  167. package/dist/modules/sandbox/workspace-root.d.mts +8 -0
  168. package/dist/modules/sandbox/workspace-root.mjs +79 -0
  169. package/dist/modules/sandbox/workspace-root.mjs.map +7 -0
  170. package/dist/modules/schemas.d.mts +121 -0
  171. package/dist/modules/schemas.mjs +146 -0
  172. package/dist/modules/schemas.mjs.map +7 -0
  173. package/dist/modules/semantic-events.d.mts +31 -0
  174. package/dist/modules/semantic-events.mjs +23 -0
  175. package/dist/modules/semantic-events.mjs.map +7 -0
  176. package/package.json +199 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/conformance/protocol.ts"],
4
+ "sourcesContent": ["// The scripted test protocol the harness conformance kit drives a harness\n// with. The kit sends prompts whose first text block is a directive; a harness\n// under test must route these to a program that implements them (the\n// reference program is `createConformanceAgent()`; a real harness runs the kit\n// against a conformance build or mode of its own program).\n//\n// conformance:echo <text>\n// Stream <text> as agent_message_chunk updates (any chunking), then end the\n// turn with state_update idle, stopReason end_turn.\n// conformance:permission\n// Send session/request_permission with at least one allow_once option,\n// then reply with the text `permission:<kind of the selected option>`\n// (`permission:cancelled` if cancelled) and end the turn.\n// conformance:elicit\n// Send elicitation/create in form mode with a required string property\n// `answer`, then reply `elicitation:<action>:<answer>` and end the turn.\n// conformance:slow\n// Stream `slow:started`, then keep the turn running until session/cancel\n// arrives and end it with stopReason cancelled.\n// conformance:error\n// End the turn with stopReason error.\n// conformance:mcp <server name> <tool> [JSON arguments]\n// Call <tool> on the session's MCP server named <server name> (over\n// `mcp/message` for an `acp` server, Streamable HTTP for an `http` one) and\n// reply `mcp:<text of the tool result>` (`mcp-error:<message>` on failure).\n// conformance:history\n// Reply with every user text the session has seen, prompts and\n// `_final/session/observe` content alike, one per line, in order.\n// conformance:wait <ms>\n// Stream `wait:started`, keep the turn running for <ms> milliseconds, stream\n// `wait:done` and end the turn (stopReason cancelled when cancelled first).\n// A prompt accepted meanwhile follows the harness's `promptWhileRunning`:\n// `inject` handles it inside this turn (one idle ends both), `queue` echoes\n// and runs it after this turn's idle.\n// conformance:clear\n// Start a fresh native conversation under the same ACP session (what\n// `/clear` does in Claude Code), reply `cleared` and end the turn. The ACP\n// session id stays the same; resuming it keeps working.\n// conformance:big <bytes>\n// Run one tool call whose result is <bytes> bytes of text plus a valid 300+ KiB\n// PNG image, then reply `big:done` and end the turn. A Final program keeps\n// every frame within the frame rule by moving both to\n// `resource_link` blocks.\n//\n// Every accepted prompt is echoed as a `user_message` (or chunk) carrying the\n// prompt response's `messageId`. A harness that declares\n// `sessionSurvivesDisconnect` keeps running work and open requests across a\n// dropped connection and re-raises open requests with the same JSON-RPC ids\n// on the next `session/resume`.\n//\n// Replay: `session/resume` with `replayFrom: {type: 'start'}` re-sends every\n// session/update of the session; with `{type: '_final/after_seq', seq: N}`\n// (final profile) every update after the first N session/update\n// notifications the client received for the session.\n\nexport const CONFORMANCE_DIRECTIVE_PREFIX = 'conformance:';\n\nexport enum ConformanceDirective {\n Echo = 'echo',\n Permission = 'permission',\n Elicit = 'elicit',\n Slow = 'slow',\n Error = 'error',\n Mcp = 'mcp',\n History = 'history',\n Wait = 'wait',\n Clear = 'clear',\n Big = 'big',\n}\n\nexport const directive = (name: ConformanceDirective, rest = ''): string =>\n `${CONFORMANCE_DIRECTIVE_PREFIX}${name}${rest === '' ? '' : ` ${rest}`}`;\n\nexport const parseDirective = (\n text: string,\n): { name: ConformanceDirective; rest: string } | null => {\n const trimmed = text.trim();\n if (!trimmed.startsWith(CONFORMANCE_DIRECTIVE_PREFIX)) return null;\n const body = trimmed.slice(CONFORMANCE_DIRECTIVE_PREFIX.length);\n const space = body.indexOf(' ');\n const head = space < 0 ? body : body.slice(0, space);\n const rest = space < 0 ? '' : body.slice(space + 1);\n const name = Object.values(ConformanceDirective).find((value) => value === head);\n return name == null ? null : { name, rest };\n};\n\nexport const CONFORMANCE_ELICITATION_ANSWER = 'conformance-answer';\nexport const CONFORMANCE_MCP_TOOL = 'echo';\nexport const CONFORMANCE_BIG_TOOL = 'large_result';\nexport const CONFORMANCE_ELICIT_TOOL = 'ask_answer';\nexport const CONFORMANCE_ACP_SERVER_NAME = 'conformance_acp';\nexport const CONFORMANCE_HTTP_SERVER_NAME = 'conformance_http';\nexport const CONFORMANCE_ACP_SERVER_ID = 'conformance-acp';\nexport const AFTER_SEQ_REPLAY_TYPE = '_final/after_seq';\nexport const OBSERVE_METHOD = '_final/session/observe';\nexport const FINAL_META_KEY = 'final.ai';\n// The image `conformance:big` attaches.\nexport { CONFORMANCE_BIG_IMAGE_BYTES } from \"./image.mjs\";\n\n// The MCP fixture's echo result text.\nexport const conformanceEchoText = (nonce: string, text: string): string =>\n `mcp-echo:${nonce}:${text}`;\n"],
5
+ "mappings": "AAuDO,MAAM,+BAA+B;AAErC,IAAK,uBAAL,kBAAKA,0BAAL;AACL,EAAAA,sBAAA,UAAO;AACP,EAAAA,sBAAA,gBAAa;AACb,EAAAA,sBAAA,YAAS;AACT,EAAAA,sBAAA,UAAO;AACP,EAAAA,sBAAA,WAAQ;AACR,EAAAA,sBAAA,SAAM;AACN,EAAAA,sBAAA,aAAU;AACV,EAAAA,sBAAA,UAAO;AACP,EAAAA,sBAAA,WAAQ;AACR,EAAAA,sBAAA,SAAM;AAVI,SAAAA;AAAA,GAAA;AAaL,MAAM,YAAY,CAAC,MAA4B,OAAO,OAC3D,GAAG,4BAA4B,GAAG,IAAI,GAAG,SAAS,KAAK,KAAK,IAAI,IAAI,EAAE;AAEjE,MAAM,iBAAiB,CAC5B,SACwD;AACxD,QAAM,UAAU,KAAK,KAAK;AAC1B,MAAI,CAAC,QAAQ,WAAW,4BAA4B,EAAG,QAAO;AAC9D,QAAM,OAAO,QAAQ,MAAM,6BAA6B,MAAM;AAC9D,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,QAAM,OAAO,QAAQ,IAAI,OAAO,KAAK,MAAM,GAAG,KAAK;AACnD,QAAM,OAAO,QAAQ,IAAI,KAAK,KAAK,MAAM,QAAQ,CAAC;AAClD,QAAM,OAAO,OAAO,OAAO,oBAAoB,EAAE,KAAK,CAAC,UAAU,UAAU,IAAI;AAC/E,SAAO,QAAQ,OAAO,OAAO,EAAE,MAAM,KAAK;AAC5C;AAEO,MAAM,iCAAiC;AACvC,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,0BAA0B;AAChC,MAAM,8BAA8B;AACpC,MAAM,+BAA+B;AACrC,MAAM,4BAA4B;AAClC,MAAM,wBAAwB;AAC9B,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAE9B,SAAS,mCAAmC;AAGrC,MAAM,sBAAsB,CAAC,OAAe,SACjD,YAAY,KAAK,IAAI,IAAI;",
6
+ "names": ["ConformanceDirective"]
7
+ }
@@ -0,0 +1,19 @@
1
+ import type { JsonValue } from "../json.mjs";
2
+ import { type AnySandboxProviderIface, type NetworkSandboxSessionIface, type SandboxPrepareContextIface, type SandboxStartContextIface } from "../sandbox/types.mjs";
3
+ import { type ConformanceCaseResultIface } from "./harness-kit.mjs";
4
+ export interface RunSandboxConformanceArgsIface {
5
+ handle?: NetworkSandboxSessionIface;
6
+ provider?: AnySandboxProviderIface;
7
+ environmentOptions?: JsonValue;
8
+ openOptions?: JsonValue;
9
+ prepareContext?: Partial<SandboxPrepareContextIface>;
10
+ startContext?: Partial<SandboxStartContextIface>;
11
+ viaWire?: boolean;
12
+ largeFileBytes?: number;
13
+ timeoutMs?: number;
14
+ }
15
+ export interface SandboxConformanceReportIface {
16
+ passed: boolean;
17
+ cases: ConformanceCaseResultIface[];
18
+ }
19
+ export declare const runSandboxConformance: (args: RunSandboxConformanceArgsIface) => Promise<SandboxConformanceReportIface>;
@@ -0,0 +1,320 @@
1
+ import { SandboxAccess } from "../manifest.mjs";
2
+ import { readAllBytes } from "../sandbox/bytes.mjs";
3
+ import { streamToString } from "../sandbox/convenience.mjs";
4
+ import { createInMemoryHandleNetwork } from "../sandbox/testing.mjs";
5
+ import {
6
+ noSandboxActingUser,
7
+ noSandboxGuides,
8
+ noSandboxRepositories,
9
+ noSandboxVariables,
10
+ SandboxEntryType
11
+ } from "../sandbox/types.mjs";
12
+ import { HandleRpcErrorCode, isHandleRpcError } from "../sandbox/wire.mjs";
13
+ import { ConformanceCaseStatus } from "./harness-kit.mjs";
14
+ const check = (condition, message) => {
15
+ if (!condition) throw new Error(message);
16
+ };
17
+ const patternBytes = (size, seed) => {
18
+ const bytes = new Uint8Array(size);
19
+ let state = seed >>> 0;
20
+ for (let i = 0; i < size; i += 1) {
21
+ state = Math.imul(state, 1664525) + 1013904223 >>> 0;
22
+ bytes[i] = state >>> 24;
23
+ }
24
+ return bytes;
25
+ };
26
+ const chunked = (bytes, chunk) => {
27
+ let offset = 0;
28
+ return new ReadableStream({
29
+ pull: (controller) => {
30
+ if (offset >= bytes.byteLength) {
31
+ controller.close();
32
+ return;
33
+ }
34
+ controller.enqueue(bytes.slice(offset, offset + chunk));
35
+ offset += chunk;
36
+ }
37
+ });
38
+ };
39
+ const sameBytes = (a, b) => a.byteLength === b.byteLength && a.every((value, i) => value === b[i]);
40
+ const withTimeout = async (promise, ms, what) => {
41
+ let timer = null;
42
+ try {
43
+ return await Promise.race([
44
+ promise,
45
+ new Promise((_, reject) => {
46
+ timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms} ms`)), ms);
47
+ })
48
+ ]);
49
+ } finally {
50
+ if (timer != null) clearTimeout(timer);
51
+ }
52
+ };
53
+ const sandboxCases = (largeFileBytes) => [
54
+ {
55
+ name: "describe",
56
+ run: async (sandbox) => {
57
+ check(sandbox.id.length > 0, "empty id");
58
+ check(
59
+ sandbox.defaultWorkingDirectory.startsWith("/"),
60
+ "defaultWorkingDirectory is not absolute"
61
+ );
62
+ check(
63
+ sandbox.resolvePath("a/b") === `${sandbox.defaultWorkingDirectory.replace(/\/$/, "")}/a/b`,
64
+ "resolvePath"
65
+ );
66
+ }
67
+ },
68
+ {
69
+ name: "text files",
70
+ run: async (sandbox, root) => {
71
+ const path = `${root}/nested/dir/a.txt`;
72
+ await sandbox.writeTextFile({ path, content: "first" });
73
+ await sandbox.writeTextFile({ path, content: "h\xE9llo\n" });
74
+ check(await sandbox.readTextFile({ path }) === "h\xE9llo\n", "text round trip");
75
+ check(
76
+ await sandbox.readTextFile({ path: `${root}/missing.txt` }) == null,
77
+ "missing text file is not null"
78
+ );
79
+ check(
80
+ await sandbox.readFile({ path: `${root}/missing.txt` }) == null,
81
+ "missing file stream is not null"
82
+ );
83
+ }
84
+ },
85
+ {
86
+ name: "ranged reads",
87
+ run: async (sandbox, root) => {
88
+ const path = `${root}/lines.txt`;
89
+ await sandbox.writeTextFile({ path, content: "l1\nl2\nl3\nl4\n" });
90
+ check(
91
+ await sandbox.readTextFile({ path, startLine: 2, endLine: 3 }) === "l2\nl3\n",
92
+ "lines 2-3"
93
+ );
94
+ check(
95
+ await sandbox.readTextFile({ path, startLine: 4, endLine: 99 }) === "l4\n",
96
+ "endLine past EOF"
97
+ );
98
+ }
99
+ },
100
+ {
101
+ name: "multi-megabyte streams",
102
+ run: async (sandbox, root) => {
103
+ const path = `${root}/large.bin`;
104
+ const content = patternBytes(largeFileBytes, largeFileBytes);
105
+ await sandbox.writeFile({ path, content: chunked(content, 3e5) });
106
+ const stream = await sandbox.readFile({ path });
107
+ check(stream != null, "large file not found");
108
+ if (stream == null) return;
109
+ check(sameBytes(await readAllBytes(stream), content), "streamed bytes differ");
110
+ const binary = await sandbox.readBinaryFile({ path });
111
+ check(binary != null && sameBytes(binary, content), "readBinaryFile differs");
112
+ const small = patternBytes(1e3, 7);
113
+ await sandbox.writeBinaryFile({ path: `${root}/small.bin`, content: small });
114
+ const back = await sandbox.readBinaryFile({ path: `${root}/small.bin` });
115
+ check(back != null && sameBytes(back, small), "binary round trip");
116
+ return `${largeFileBytes} bytes`;
117
+ }
118
+ },
119
+ {
120
+ name: "stat, readDir, removePath",
121
+ run: async (sandbox, root) => {
122
+ await sandbox.writeTextFile({ path: `${root}/tree/x.txt`, content: "xyz" });
123
+ await sandbox.writeTextFile({ path: `${root}/tree/sub/y.txt`, content: "y" });
124
+ const stat = await sandbox.stat({ path: `${root}/tree/x.txt` });
125
+ check(stat?.type === SandboxEntryType.File && stat.size === 3, "file stat");
126
+ check(
127
+ (await sandbox.stat({ path: `${root}/tree` }))?.type === SandboxEntryType.Directory,
128
+ "directory stat"
129
+ );
130
+ check(
131
+ await sandbox.stat({ path: `${root}/tree/none` }) == null,
132
+ "missing stat is not null"
133
+ );
134
+ const names = (await sandbox.readDir({ path: `${root}/tree` })).map((entry) => entry.name).sort();
135
+ check(
136
+ JSON.stringify(names) === JSON.stringify(["sub", "x.txt"]),
137
+ `readDir returned ${names.join(",")}`
138
+ );
139
+ await sandbox.removePath({ path: `${root}/tree/none`, force: true });
140
+ await sandbox.removePath({ path: `${root}/tree`, recursive: true });
141
+ check(
142
+ await sandbox.stat({ path: `${root}/tree/x.txt` }) == null,
143
+ "recursive remove left files"
144
+ );
145
+ }
146
+ },
147
+ {
148
+ name: "process output and exit codes",
149
+ run: async (sandbox) => {
150
+ const echo = await sandbox.run({ command: "echo", args: ["hello", "world"] });
151
+ check(
152
+ echo.exitCode === 0 && echo.stdout === "hello world\n",
153
+ `echo: ${JSON.stringify(echo)}`
154
+ );
155
+ const shell = await sandbox.run({ command: "echo out; echo err >&2; exit 3", shell: true });
156
+ check(
157
+ shell.exitCode === 3 && shell.stdout === "out\n" && shell.stderr === "err\n",
158
+ `shell: ${JSON.stringify(shell)}`
159
+ );
160
+ }
161
+ },
162
+ {
163
+ name: "process stdin",
164
+ run: async (sandbox) => {
165
+ const piped = await sandbox.run({ command: "cat", stdin: "from stdin" });
166
+ check(piped.stdout === "from stdin", `run stdin: ${JSON.stringify(piped)}`);
167
+ const cat = await sandbox.spawn({ command: "cat" });
168
+ const output = streamToString(cat.stdout);
169
+ await cat.write("a");
170
+ await cat.write("b", { end: true });
171
+ check(await output === "ab", "spawned cat echoed wrong data");
172
+ check((await cat.wait()).exitCode === 0, "cat exit code");
173
+ }
174
+ },
175
+ {
176
+ name: "process kill",
177
+ run: async (sandbox) => {
178
+ const sleeper = await sandbox.spawn({ command: "sleep", args: ["30"] });
179
+ await sleeper.kill();
180
+ const result = await withTimeout(sleeper.wait(), 5e3, "wait after kill");
181
+ check(result.exitCode !== 0, "killed process exited 0");
182
+ }
183
+ }
184
+ ];
185
+ const startFromProvider = async (args, provider) => {
186
+ const implementation = provider.definition.environment(args.environmentOptions);
187
+ const prepareContext = {
188
+ files: {},
189
+ resources: {},
190
+ sourceRevision: "conformance",
191
+ storagePath: "/tmp/final-conformance",
192
+ organizationId: "conformance",
193
+ guides: noSandboxGuides,
194
+ actingUser: noSandboxActingUser,
195
+ ...args.prepareContext
196
+ };
197
+ const artifact = await implementation.prepare(prepareContext);
198
+ const startContext = {
199
+ session: {
200
+ id: crypto.randomUUID(),
201
+ organizationId: "conformance",
202
+ conversationId: null,
203
+ definitionId: "conformance",
204
+ bindingId: crypto.randomUUID(),
205
+ target: "conformance"
206
+ },
207
+ storagePath: "/tmp/final-conformance",
208
+ guides: noSandboxGuides,
209
+ variables: noSandboxVariables,
210
+ repositories: noSandboxRepositories,
211
+ actingUser: noSandboxActingUser,
212
+ ...args.startContext
213
+ };
214
+ const { handle } = await implementation.start(startContext, args.openOptions, artifact);
215
+ return handle;
216
+ };
217
+ const runSandboxConformance = async (args) => {
218
+ const timeoutMs = args.timeoutMs ?? 3e4;
219
+ const cases = [];
220
+ const record = async (name, run) => {
221
+ const started2 = Date.now();
222
+ try {
223
+ const detail = await withTimeout(run(), timeoutMs, name);
224
+ cases.push({
225
+ name,
226
+ status: ConformanceCaseStatus.Passed,
227
+ durationMs: Date.now() - started2,
228
+ error: null,
229
+ detail: detail ?? null
230
+ });
231
+ } catch (error) {
232
+ cases.push({
233
+ name,
234
+ status: ConformanceCaseStatus.Failed,
235
+ durationMs: Date.now() - started2,
236
+ error: error instanceof Error ? error.message : String(error),
237
+ detail: null
238
+ });
239
+ }
240
+ };
241
+ const started = { handle: null };
242
+ let sandbox = args.handle ?? null;
243
+ if (sandbox == null) {
244
+ if (args.provider == null)
245
+ throw new Error("runSandboxConformance needs a handle or a provider");
246
+ const provider = args.provider;
247
+ await record("provider start", async () => {
248
+ started.handle = await startFromProvider(args, provider);
249
+ });
250
+ sandbox = started.handle?.sandbox ?? null;
251
+ }
252
+ if (sandbox == null) return { passed: false, cases };
253
+ const target = sandbox;
254
+ const root = target.resolvePath(`conformance-${crypto.randomUUID().slice(0, 8)}`);
255
+ const list = sandboxCases(args.largeFileBytes ?? 5 * 1024 * 1024);
256
+ for (const entry of list) await record(entry.name, () => entry.run(target, root));
257
+ if (args.viaWire !== false) {
258
+ const network = createInMemoryHandleNetwork();
259
+ network.serve({ url: "mem://conformance", sandbox: target });
260
+ network.serve({
261
+ url: "mem://conformance-read",
262
+ sandbox: target,
263
+ access: { mode: SandboxAccess.Read, lifecycle: false }
264
+ });
265
+ const client = await network.connect({ url: "mem://conformance", token: "conformance" });
266
+ try {
267
+ const wireRoot = `${root}-wire`;
268
+ for (const entry of list)
269
+ await record(`handle rpc: ${entry.name}`, () => entry.run(client, wireRoot));
270
+ await record("handle rpc: read-only access policy", async () => {
271
+ const readOnly = await network.connect({
272
+ url: "mem://conformance-read",
273
+ token: "conformance"
274
+ });
275
+ try {
276
+ await target.writeTextFile({ path: `${root}/ro.txt`, content: "visible" });
277
+ check(
278
+ await readOnly.readTextFile({ path: `${root}/ro.txt` }) === "visible",
279
+ "read failed on a read binding"
280
+ );
281
+ const attempts = [
282
+ [
283
+ "writeTextFile",
284
+ readOnly.writeTextFile({ path: `${root}/ro.txt`, content: "changed" })
285
+ ],
286
+ ["run", readOnly.run({ command: "echo" })],
287
+ ["spawn", readOnly.spawn({ command: "echo" })],
288
+ ["removePath", readOnly.removePath({ path: `${root}/ro.txt` })]
289
+ ];
290
+ for (const [what, attempt] of attempts) {
291
+ const error = await attempt.then(
292
+ () => null,
293
+ (cause) => cause
294
+ );
295
+ check(
296
+ isHandleRpcError(error) && error.code === HandleRpcErrorCode.Forbidden,
297
+ `${what} was not rejected`
298
+ );
299
+ }
300
+ check(
301
+ await target.readTextFile({ path: `${root}/ro.txt` }) === "visible",
302
+ "a read binding changed the file"
303
+ );
304
+ } finally {
305
+ readOnly.close();
306
+ }
307
+ });
308
+ } finally {
309
+ client.close();
310
+ await target.removePath({ path: `${root}-wire`, recursive: true, force: true }).catch(() => void 0);
311
+ }
312
+ }
313
+ await target.removePath({ path: root, recursive: true, force: true }).catch(() => void 0);
314
+ await started.handle?.onSandboxDelete({ reason: "conformance" }).catch(() => void 0);
315
+ return { passed: cases.every((entry) => entry.status !== ConformanceCaseStatus.Failed), cases };
316
+ };
317
+ export {
318
+ runSandboxConformance
319
+ };
320
+ //# sourceMappingURL=sandbox-kit.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/conformance/sandbox-kit.ts"],
4
+ "sourcesContent": ["import type { JsonValue } from \"../json.mjs\";\nimport { SandboxAccess } from \"../manifest.mjs\";\nimport { readAllBytes } from \"../sandbox/bytes.mjs\";\nimport { streamToString } from \"../sandbox/convenience.mjs\";\nimport { createInMemoryHandleNetwork } from \"../sandbox/testing.mjs\";\nimport {\n noSandboxActingUser,\n noSandboxGuides,\n noSandboxRepositories,\n noSandboxVariables,\n SandboxEntryType,\n type AnySandboxProviderIface,\n type NetworkSandboxSessionIface,\n type SandboxPrepareContextIface,\n type SandboxProviderHandleIface,\n type SandboxStartContextIface,\n} from \"../sandbox/types.mjs\";\nimport { HandleRpcErrorCode, isHandleRpcError } from \"../sandbox/wire.mjs\";\nimport { ConformanceCaseStatus, type ConformanceCaseResultIface } from \"./harness-kit.mjs\";\n\n// Sandbox conformance: files, streams with\n// multi-megabyte content, ranged reads, process I/O and kill, run directly on\n// the handle and again through the handle RPC (client and server over an\n// in-memory connection), plus the server's read-only access policy. Commands\n// used: echo, cat, sleep and `exit` under `shell: true`.\n\nexport interface RunSandboxConformanceArgsIface {\n // Either a started handle, or a provider the kit prepares and starts.\n handle?: NetworkSandboxSessionIface;\n provider?: AnySandboxProviderIface;\n environmentOptions?: JsonValue;\n openOptions?: JsonValue;\n prepareContext?: Partial<SandboxPrepareContextIface>;\n startContext?: Partial<SandboxStartContextIface>;\n // Also run the cases through the handle RPC (default true).\n viaWire?: boolean;\n largeFileBytes?: number;\n timeoutMs?: number;\n}\n\nexport interface SandboxConformanceReportIface {\n passed: boolean;\n cases: ConformanceCaseResultIface[];\n}\n\ninterface StartedProviderIface {\n handle: SandboxProviderHandleIface<NetworkSandboxSessionIface> | null;\n}\n\nconst check = (condition: boolean, message: string) => {\n if (!condition) throw new Error(message);\n};\n\nconst patternBytes = (size: number, seed: number): Uint8Array => {\n const bytes = new Uint8Array(size);\n let state = seed >>> 0;\n for (let i = 0; i < size; i += 1) {\n state = (Math.imul(state, 1664525) + 1013904223) >>> 0;\n bytes[i] = state >>> 24;\n }\n return bytes;\n};\n\nconst chunked = (bytes: Uint8Array, chunk: number): ReadableStream<Uint8Array> => {\n let offset = 0;\n return new ReadableStream<Uint8Array>({\n pull: (controller) => {\n if (offset >= bytes.byteLength) {\n controller.close();\n return;\n }\n controller.enqueue(bytes.slice(offset, offset + chunk));\n offset += chunk;\n },\n });\n};\n\nconst sameBytes = (a: Uint8Array, b: Uint8Array) =>\n a.byteLength === b.byteLength && a.every((value, i) => value === b[i]);\n\nconst withTimeout = async <T>(promise: Promise<T>, ms: number, what: string): Promise<T> => {\n let timer: ReturnType<typeof setTimeout> | null = null;\n try {\n return await Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms} ms`)), ms);\n }),\n ]);\n } finally {\n if (timer != null) clearTimeout(timer);\n }\n};\n\ntype SandboxCase = {\n name: string;\n run: (sandbox: NetworkSandboxSessionIface, root: string) => Promise<string | void>;\n};\n\nconst sandboxCases = (largeFileBytes: number): SandboxCase[] => [\n {\n name: 'describe',\n run: async (sandbox) => {\n check(sandbox.id.length > 0, 'empty id');\n check(\n sandbox.defaultWorkingDirectory.startsWith('/'),\n 'defaultWorkingDirectory is not absolute',\n );\n check(\n sandbox.resolvePath('a/b') === `${sandbox.defaultWorkingDirectory.replace(/\\/$/, '')}/a/b`,\n 'resolvePath',\n );\n },\n },\n {\n name: 'text files',\n run: async (sandbox, root) => {\n const path = `${root}/nested/dir/a.txt`;\n await sandbox.writeTextFile({ path, content: 'first' });\n await sandbox.writeTextFile({ path, content: 'h\u00E9llo\\n' });\n check((await sandbox.readTextFile({ path })) === 'h\u00E9llo\\n', 'text round trip');\n check(\n (await sandbox.readTextFile({ path: `${root}/missing.txt` })) == null,\n 'missing text file is not null',\n );\n check(\n (await sandbox.readFile({ path: `${root}/missing.txt` })) == null,\n 'missing file stream is not null',\n );\n },\n },\n {\n name: 'ranged reads',\n run: async (sandbox, root) => {\n const path = `${root}/lines.txt`;\n await sandbox.writeTextFile({ path, content: 'l1\\nl2\\nl3\\nl4\\n' });\n check(\n (await sandbox.readTextFile({ path, startLine: 2, endLine: 3 })) === 'l2\\nl3\\n',\n 'lines 2-3',\n );\n check(\n (await sandbox.readTextFile({ path, startLine: 4, endLine: 99 })) === 'l4\\n',\n 'endLine past EOF',\n );\n },\n },\n {\n name: 'multi-megabyte streams',\n run: async (sandbox, root) => {\n const path = `${root}/large.bin`;\n const content = patternBytes(largeFileBytes, largeFileBytes);\n await sandbox.writeFile({ path, content: chunked(content, 300_000) });\n const stream = await sandbox.readFile({ path });\n check(stream != null, 'large file not found');\n if (stream == null) return;\n check(sameBytes(await readAllBytes(stream), content), 'streamed bytes differ');\n const binary = await sandbox.readBinaryFile({ path });\n check(binary != null && sameBytes(binary, content), 'readBinaryFile differs');\n const small = patternBytes(1000, 7);\n await sandbox.writeBinaryFile({ path: `${root}/small.bin`, content: small });\n const back = await sandbox.readBinaryFile({ path: `${root}/small.bin` });\n check(back != null && sameBytes(back, small), 'binary round trip');\n return `${largeFileBytes} bytes`;\n },\n },\n {\n name: 'stat, readDir, removePath',\n run: async (sandbox, root) => {\n await sandbox.writeTextFile({ path: `${root}/tree/x.txt`, content: 'xyz' });\n await sandbox.writeTextFile({ path: `${root}/tree/sub/y.txt`, content: 'y' });\n const stat = await sandbox.stat({ path: `${root}/tree/x.txt` });\n check(stat?.type === SandboxEntryType.File && stat.size === 3, 'file stat');\n check(\n (await sandbox.stat({ path: `${root}/tree` }))?.type === SandboxEntryType.Directory,\n 'directory stat',\n );\n check(\n (await sandbox.stat({ path: `${root}/tree/none` })) == null,\n 'missing stat is not null',\n );\n const names = (await sandbox.readDir({ path: `${root}/tree` }))\n .map((entry) => entry.name)\n .sort();\n check(\n JSON.stringify(names) === JSON.stringify(['sub', 'x.txt']),\n `readDir returned ${names.join(',')}`,\n );\n await sandbox.removePath({ path: `${root}/tree/none`, force: true });\n await sandbox.removePath({ path: `${root}/tree`, recursive: true });\n check(\n (await sandbox.stat({ path: `${root}/tree/x.txt` })) == null,\n 'recursive remove left files',\n );\n },\n },\n {\n name: 'process output and exit codes',\n run: async (sandbox) => {\n const echo = await sandbox.run({ command: 'echo', args: ['hello', 'world'] });\n check(\n echo.exitCode === 0 && echo.stdout === 'hello world\\n',\n `echo: ${JSON.stringify(echo)}`,\n );\n const shell = await sandbox.run({ command: 'echo out; echo err >&2; exit 3', shell: true });\n check(\n shell.exitCode === 3 && shell.stdout === 'out\\n' && shell.stderr === 'err\\n',\n `shell: ${JSON.stringify(shell)}`,\n );\n },\n },\n {\n name: 'process stdin',\n run: async (sandbox) => {\n const piped = await sandbox.run({ command: 'cat', stdin: 'from stdin' });\n check(piped.stdout === 'from stdin', `run stdin: ${JSON.stringify(piped)}`);\n const cat = await sandbox.spawn({ command: 'cat' });\n const output = streamToString(cat.stdout);\n await cat.write('a');\n await cat.write('b', { end: true });\n check((await output) === 'ab', 'spawned cat echoed wrong data');\n check((await cat.wait()).exitCode === 0, 'cat exit code');\n },\n },\n {\n name: 'process kill',\n run: async (sandbox) => {\n const sleeper = await sandbox.spawn({ command: 'sleep', args: ['30'] });\n await sleeper.kill();\n const result = await withTimeout(sleeper.wait(), 5_000, 'wait after kill');\n check(result.exitCode !== 0, 'killed process exited 0');\n },\n },\n];\n\nconst startFromProvider = async (\n args: RunSandboxConformanceArgsIface,\n provider: AnySandboxProviderIface,\n): Promise<SandboxProviderHandleIface<NetworkSandboxSessionIface>> => {\n const implementation = provider.definition.environment(args.environmentOptions);\n const prepareContext: SandboxPrepareContextIface = {\n files: {},\n resources: {},\n sourceRevision: 'conformance',\n storagePath: '/tmp/final-conformance',\n organizationId: 'conformance',\n guides: noSandboxGuides,\n actingUser: noSandboxActingUser,\n ...args.prepareContext,\n };\n const artifact = await implementation.prepare(prepareContext);\n const startContext: SandboxStartContextIface = {\n session: {\n id: crypto.randomUUID(),\n organizationId: 'conformance',\n conversationId: null,\n definitionId: 'conformance',\n bindingId: crypto.randomUUID(),\n target: 'conformance',\n },\n storagePath: '/tmp/final-conformance',\n guides: noSandboxGuides,\n variables: noSandboxVariables,\n repositories: noSandboxRepositories,\n actingUser: noSandboxActingUser,\n ...args.startContext,\n };\n const { handle } = await implementation.start(startContext, args.openOptions, artifact);\n return handle;\n};\n\nexport const runSandboxConformance = async (\n args: RunSandboxConformanceArgsIface,\n): Promise<SandboxConformanceReportIface> => {\n const timeoutMs = args.timeoutMs ?? 30_000;\n const cases: ConformanceCaseResultIface[] = [];\n const record = async (name: string, run: () => Promise<string | void>) => {\n const started = Date.now();\n try {\n const detail = await withTimeout(run(), timeoutMs, name);\n cases.push({\n name,\n status: ConformanceCaseStatus.Passed,\n durationMs: Date.now() - started,\n error: null,\n detail: detail ?? null,\n });\n } catch (error) {\n cases.push({\n name,\n status: ConformanceCaseStatus.Failed,\n durationMs: Date.now() - started,\n error: error instanceof Error ? error.message : String(error),\n detail: null,\n });\n }\n };\n\n // A holder, so the assignment inside the closure is visible to the narrowing.\n const started: StartedProviderIface = { handle: null };\n let sandbox = args.handle ?? null;\n if (sandbox == null) {\n if (args.provider == null)\n throw new Error('runSandboxConformance needs a handle or a provider');\n const provider = args.provider;\n await record('provider start', async () => {\n started.handle = await startFromProvider(args, provider);\n });\n sandbox = started.handle?.sandbox ?? null;\n }\n if (sandbox == null) return { passed: false, cases };\n const target = sandbox;\n\n const root = target.resolvePath(`conformance-${crypto.randomUUID().slice(0, 8)}`);\n const list = sandboxCases(args.largeFileBytes ?? 5 * 1024 * 1024);\n for (const entry of list) await record(entry.name, () => entry.run(target, root));\n\n if (args.viaWire !== false) {\n const network = createInMemoryHandleNetwork();\n network.serve({ url: 'mem://conformance', sandbox: target });\n network.serve({\n url: 'mem://conformance-read',\n sandbox: target,\n access: { mode: SandboxAccess.Read, lifecycle: false },\n });\n const client = await network.connect({ url: 'mem://conformance', token: 'conformance' });\n try {\n const wireRoot = `${root}-wire`;\n for (const entry of list)\n await record(`handle rpc: ${entry.name}`, () => entry.run(client, wireRoot));\n await record('handle rpc: read-only access policy', async () => {\n const readOnly = await network.connect({\n url: 'mem://conformance-read',\n token: 'conformance',\n });\n try {\n await target.writeTextFile({ path: `${root}/ro.txt`, content: 'visible' });\n check(\n (await readOnly.readTextFile({ path: `${root}/ro.txt` })) === 'visible',\n 'read failed on a read binding',\n );\n const attempts: [string, Promise<unknown>][] = [\n [\n 'writeTextFile',\n readOnly.writeTextFile({ path: `${root}/ro.txt`, content: 'changed' }),\n ],\n ['run', readOnly.run({ command: 'echo' })],\n ['spawn', readOnly.spawn({ command: 'echo' })],\n ['removePath', readOnly.removePath({ path: `${root}/ro.txt` })],\n ];\n for (const [what, attempt] of attempts) {\n const error = await attempt.then(\n () => null,\n (cause: unknown) => cause,\n );\n check(\n isHandleRpcError(error) && error.code === HandleRpcErrorCode.Forbidden,\n `${what} was not rejected`,\n );\n }\n check(\n (await target.readTextFile({ path: `${root}/ro.txt` })) === 'visible',\n 'a read binding changed the file',\n );\n } finally {\n readOnly.close();\n }\n });\n } finally {\n client.close();\n await target\n .removePath({ path: `${root}-wire`, recursive: true, force: true })\n .catch(() => undefined);\n }\n }\n\n await target.removePath({ path: root, recursive: true, force: true }).catch(() => undefined);\n await started.handle?.onSandboxDelete({ reason: 'conformance' }).catch(() => undefined);\n return { passed: cases.every((entry) => entry.status !== ConformanceCaseStatus.Failed), cases };\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["started"]
7
+ }
@@ -0,0 +1,30 @@
1
+ import * as z from 'zod';
2
+ import { type ScriptedAction, type ScriptedConversationIface } from "./directives.mjs";
3
+ import { type HeldStreamTimingIface } from "./sse.mjs";
4
+ export declare const chatCompletionsRequestSchema: z.ZodObject<{
5
+ model: z.ZodString;
6
+ stream: z.ZodOptional<z.ZodBoolean>;
7
+ messages: z.ZodArray<z.ZodObject<{
8
+ role: z.ZodString;
9
+ content: z.ZodDefault<z.ZodUnion<readonly [z.ZodString, z.ZodPipe<z.ZodArray<z.ZodObject<{
10
+ type: z.ZodString;
11
+ text: z.ZodOptional<z.ZodString>;
12
+ }, z.core.$loose>>, z.ZodTransform<string, {
13
+ [x: string]: unknown;
14
+ type: string;
15
+ text?: string | undefined;
16
+ }[]>>, z.ZodPipe<z.ZodNull, z.ZodTransform<string, null>>]>>;
17
+ }, z.core.$loose>>;
18
+ tools: z.ZodOptional<z.ZodArray<z.ZodObject<{
19
+ function: z.ZodOptional<z.ZodObject<{
20
+ name: z.ZodString;
21
+ }, z.core.$loose>>;
22
+ }, z.core.$loose>>>;
23
+ }, z.core.$loose>;
24
+ export type ChatCompletionsRequest = z.infer<typeof chatCompletionsRequestSchema>;
25
+ export declare const chatCompletionsConversation: (request: ChatCompletionsRequest) => ScriptedConversationIface;
26
+ export declare const chatCompletionsResponse: (args: {
27
+ request: ChatCompletionsRequest;
28
+ action: ScriptedAction;
29
+ timing: HeldStreamTimingIface;
30
+ }) => Response;
@@ -0,0 +1,125 @@
1
+ import * as z from "zod";
2
+ import {
3
+ ConversationEntryKind,
4
+ qualifiedToolName,
5
+ replyPlan,
6
+ ScriptedActionKind
7
+ } from "./directives.mjs";
8
+ import {
9
+ heldSseResponse,
10
+ jsonResponse,
11
+ pausedSseResponse,
12
+ sseEvent,
13
+ sseResponse
14
+ } from "./sse.mjs";
15
+ const partSchema = z.looseObject({ type: z.string(), text: z.string().optional() });
16
+ const contentTextSchema = z.union([
17
+ z.string(),
18
+ z.array(partSchema).transform((parts) => parts.map((part) => part.text ?? "").join("")),
19
+ z.null().transform(() => "")
20
+ ]).default("");
21
+ const chatCompletionsRequestSchema = z.looseObject({
22
+ model: z.string(),
23
+ stream: z.boolean().optional(),
24
+ messages: z.array(z.looseObject({ role: z.string(), content: contentTextSchema })),
25
+ tools: z.array(z.looseObject({ function: z.looseObject({ name: z.string() }).optional() })).optional()
26
+ });
27
+ const chatCompletionsConversation = (request) => ({
28
+ entries: request.messages.flatMap((message) => {
29
+ if (message.role === "user") {
30
+ return [{ kind: ConversationEntryKind.User, text: message.content }];
31
+ }
32
+ if (message.role === "tool") {
33
+ return [{ kind: ConversationEntryKind.ToolResult, text: message.content, isError: false }];
34
+ }
35
+ return [];
36
+ }),
37
+ tools: (request.tools ?? []).flatMap(
38
+ (tool) => tool.function == null ? [] : [{ name: tool.function.name, namespace: null }]
39
+ ),
40
+ toolSearch: false,
41
+ codeMode: null
42
+ });
43
+ const USAGE = { prompt_tokens: 12, completion_tokens: 4, total_tokens: 16 };
44
+ const chatCompletionsResponse = (args) => {
45
+ const { request, action } = args;
46
+ if (action.kind === ScriptedActionKind.Error) {
47
+ return jsonResponse(400, {
48
+ error: { message: action.message, type: "invalid_request_error", param: null, code: null }
49
+ });
50
+ }
51
+ const id = `chatcmpl-${crypto.randomUUID()}`;
52
+ const created = Math.floor(Date.now() / 1e3);
53
+ const toolCall = action.kind === ScriptedActionKind.ToolCall ? {
54
+ id: `call_${crypto.randomUUID()}`,
55
+ name: qualifiedToolName(action.tool),
56
+ arguments: JSON.stringify(action.arguments)
57
+ } : null;
58
+ const plan = replyPlan(action);
59
+ const finishReason = toolCall == null ? "stop" : "tool_calls";
60
+ if (request.stream !== true) {
61
+ const message = {
62
+ role: "assistant",
63
+ content: plan == null ? "" : `${plan.first}${plan.second}`
64
+ };
65
+ if (toolCall != null) {
66
+ message.tool_calls = [
67
+ {
68
+ id: toolCall.id,
69
+ type: "function",
70
+ function: { name: toolCall.name, arguments: toolCall.arguments }
71
+ }
72
+ ];
73
+ }
74
+ return jsonResponse(200, {
75
+ id,
76
+ object: "chat.completion",
77
+ created,
78
+ model: request.model,
79
+ choices: [{ index: 0, message, finish_reason: finishReason }],
80
+ usage: USAGE
81
+ });
82
+ }
83
+ const chunk = (choices, extra = {}) => sseEvent(null, {
84
+ id,
85
+ object: "chat.completion.chunk",
86
+ created,
87
+ model: request.model,
88
+ choices,
89
+ ...extra
90
+ });
91
+ const delta = (value, finish = null) => chunk([{ index: 0, delta: value, finish_reason: finish }]);
92
+ const content = (text) => text === "" ? [] : [delta({ content: text })];
93
+ const end = [
94
+ delta({}, finishReason),
95
+ chunk([], { usage: USAGE }),
96
+ { event: null, data: "[DONE]" }
97
+ ];
98
+ const head = [delta({ role: "assistant", content: "" })];
99
+ if (toolCall != null) {
100
+ head.push(
101
+ delta({
102
+ tool_calls: [
103
+ {
104
+ index: 0,
105
+ id: toolCall.id,
106
+ type: "function",
107
+ function: { name: toolCall.name, arguments: "" }
108
+ }
109
+ ]
110
+ }),
111
+ delta({ tool_calls: [{ index: 0, function: { arguments: toolCall.arguments } }] })
112
+ );
113
+ return sseResponse([...head, ...end]);
114
+ }
115
+ if (plan == null) return sseResponse([...head, ...end]);
116
+ head.push(...content(plan.first));
117
+ if (plan.held) return heldSseResponse(head, args.timing);
118
+ return pausedSseResponse(head, [...content(plan.second), ...end], plan.pauseMs);
119
+ };
120
+ export {
121
+ chatCompletionsConversation,
122
+ chatCompletionsRequestSchema,
123
+ chatCompletionsResponse
124
+ };
125
+ //# sourceMappingURL=chat-completions.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/conformance/scripted-model/chat-completions.ts"],
4
+ "sourcesContent": ["import * as z from 'zod';\nimport type { JsonObject, JsonValue } from \"../../json.mjs\";\nimport {\n ConversationEntryKind,\n qualifiedToolName,\n replyPlan,\n ScriptedActionKind,\n type ConversationEntry,\n type ScriptedAction,\n type ScriptedConversationIface,\n} from \"./directives.mjs\";\nimport {\n heldSseResponse,\n jsonResponse,\n pausedSseResponse,\n sseEvent,\n sseResponse,\n type HeldStreamTimingIface,\n type SseEventIface,\n} from \"./sse.mjs\";\n\n// OpenAI / OpenRouter chat completions (`POST \u2026/chat/completions`), as Pi\n// (serverless and remote) speaks it.\n\nconst partSchema = z.looseObject({ type: z.string(), text: z.string().optional() });\n// Message content as text: a string, text parts, or none (tool-call turns).\nconst contentTextSchema = z\n .union([\n z.string(),\n z.array(partSchema).transform((parts) => parts.map((part) => part.text ?? '').join('')),\n z.null().transform(() => ''),\n ])\n .default('');\n\nexport const chatCompletionsRequestSchema = z.looseObject({\n model: z.string(),\n stream: z.boolean().optional(),\n messages: z.array(z.looseObject({ role: z.string(), content: contentTextSchema })),\n tools: z\n .array(z.looseObject({ function: z.looseObject({ name: z.string() }).optional() }))\n .optional(),\n});\n\nexport type ChatCompletionsRequest = z.infer<typeof chatCompletionsRequestSchema>;\n\nexport const chatCompletionsConversation = (\n request: ChatCompletionsRequest,\n): ScriptedConversationIface => ({\n entries: request.messages.flatMap((message): ConversationEntry[] => {\n if (message.role === 'user') {\n return [{ kind: ConversationEntryKind.User, text: message.content }];\n }\n if (message.role === 'tool') {\n return [{ kind: ConversationEntryKind.ToolResult, text: message.content, isError: false }];\n }\n return [];\n }),\n tools: (request.tools ?? []).flatMap((tool) =>\n tool.function == null ? [] : [{ name: tool.function.name, namespace: null }],\n ),\n toolSearch: false,\n codeMode: null,\n});\n\nconst USAGE = { prompt_tokens: 12, completion_tokens: 4, total_tokens: 16 };\n\nexport const chatCompletionsResponse = (args: {\n request: ChatCompletionsRequest;\n action: ScriptedAction;\n timing: HeldStreamTimingIface;\n}): Response => {\n const { request, action } = args;\n if (action.kind === ScriptedActionKind.Error) {\n return jsonResponse(400, {\n error: { message: action.message, type: 'invalid_request_error', param: null, code: null },\n });\n }\n const id = `chatcmpl-${crypto.randomUUID()}`;\n const created = Math.floor(Date.now() / 1000);\n // Chat completions has no tool search; the decision never asks for it here.\n const toolCall =\n action.kind === ScriptedActionKind.ToolCall\n ? {\n id: `call_${crypto.randomUUID()}`,\n name: qualifiedToolName(action.tool),\n arguments: JSON.stringify(action.arguments),\n }\n : null;\n const plan = replyPlan(action);\n const finishReason = toolCall == null ? 'stop' : 'tool_calls';\n if (request.stream !== true) {\n const message: JsonObject = {\n role: 'assistant',\n content: plan == null ? '' : `${plan.first}${plan.second}`,\n };\n if (toolCall != null) {\n message.tool_calls = [\n {\n id: toolCall.id,\n type: 'function',\n function: { name: toolCall.name, arguments: toolCall.arguments },\n },\n ];\n }\n return jsonResponse(200, {\n id,\n object: 'chat.completion',\n created,\n model: request.model,\n choices: [{ index: 0, message, finish_reason: finishReason }],\n usage: USAGE,\n });\n }\n const chunk = (choices: JsonValue[], extra: JsonObject = {}): SseEventIface =>\n sseEvent(null, {\n id,\n object: 'chat.completion.chunk',\n created,\n model: request.model,\n choices,\n ...extra,\n });\n const delta = (value: JsonObject, finish: string | null = null): SseEventIface =>\n chunk([{ index: 0, delta: value, finish_reason: finish }]);\n const content = (text: string): SseEventIface[] =>\n text === '' ? [] : [delta({ content: text })];\n const end: SseEventIface[] = [\n delta({}, finishReason),\n chunk([], { usage: USAGE }),\n { event: null, data: '[DONE]' },\n ];\n const head: SseEventIface[] = [delta({ role: 'assistant', content: '' })];\n if (toolCall != null) {\n head.push(\n delta({\n tool_calls: [\n {\n index: 0,\n id: toolCall.id,\n type: 'function',\n function: { name: toolCall.name, arguments: '' },\n },\n ],\n }),\n delta({ tool_calls: [{ index: 0, function: { arguments: toolCall.arguments } }] }),\n );\n return sseResponse([...head, ...end]);\n }\n if (plan == null) return sseResponse([...head, ...end]);\n head.push(...content(plan.first));\n if (plan.held) return heldSseResponse(head, args.timing);\n return pausedSseResponse(head, [...content(plan.second), ...end], plan.pauseMs);\n};\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }