@noetic-tools/core 0.0.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 (207) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +38 -0
  3. package/dist/adapters/in-memory-fs-adapter.d.ts +3 -0
  4. package/dist/adapters/in-memory-fs-adapter.js +245 -0
  5. package/dist/adapters/in-memory-shell-adapter.d.ts +9 -0
  6. package/dist/adapters/in-memory-shell-adapter.js +20 -0
  7. package/dist/adapters/in-memory-subprocess-adapter.d.ts +5 -0
  8. package/dist/adapters/in-memory-subprocess-adapter.js +126 -0
  9. package/dist/adapters/local-fs-adapter.d.ts +3 -0
  10. package/dist/adapters/local-fs-adapter.js +43 -0
  11. package/dist/adapters/local-shell-adapter.d.ts +51 -0
  12. package/dist/adapters/local-shell-adapter.js +167 -0
  13. package/dist/adapters/local-subprocess-adapter.d.ts +16 -0
  14. package/dist/adapters/local-subprocess-adapter.js +187 -0
  15. package/dist/adapters/openrouter.d.ts +57 -0
  16. package/dist/adapters/openrouter.js +248 -0
  17. package/dist/builders/channel-builder.d.ts +36 -0
  18. package/dist/builders/channel-builder.js +15 -0
  19. package/dist/builders/control-flow-builders.d.ts +79 -0
  20. package/dist/builders/control-flow-builders.js +53 -0
  21. package/dist/builders/every.d.ts +45 -0
  22. package/dist/builders/every.js +60 -0
  23. package/dist/builders/layer-provides-builders.d.ts +32 -0
  24. package/dist/builders/layer-provides-builders.js +33 -0
  25. package/dist/builders/loop-builder.d.ts +23 -0
  26. package/dist/builders/loop-builder.js +46 -0
  27. package/dist/builders/memory-builder.d.ts +11 -0
  28. package/dist/builders/memory-builder.js +15 -0
  29. package/dist/builders/provide-builder.d.ts +20 -0
  30. package/dist/builders/provide-builder.js +34 -0
  31. package/dist/builders/spawn-builder.d.ts +20 -0
  32. package/dist/builders/spawn-builder.js +33 -0
  33. package/dist/builders/step-builders.d.ts +62 -0
  34. package/dist/builders/step-builders.js +99 -0
  35. package/dist/builders/tool-builder.d.ts +52 -0
  36. package/dist/builders/tool-builder.js +59 -0
  37. package/dist/conditions/conditions.d.ts +114 -0
  38. package/dist/conditions/conditions.js +271 -0
  39. package/dist/conditions/cosine-similarity.d.ts +11 -0
  40. package/dist/conditions/cosine-similarity.js +28 -0
  41. package/dist/conditions/index.d.ts +3 -0
  42. package/dist/conditions/index.js +2 -0
  43. package/dist/errors/noetic-config-error.d.ts +41 -0
  44. package/dist/errors/noetic-config-error.js +44 -0
  45. package/dist/errors/noetic-error.d.ts +18 -0
  46. package/dist/errors/noetic-error.js +52 -0
  47. package/dist/index.d.ts +152 -0
  48. package/dist/index.js +112 -0
  49. package/dist/internal-test.d.ts +11 -0
  50. package/dist/internal-test.js +11 -0
  51. package/dist/interpreter/clone-guard.d.ts +4 -0
  52. package/dist/interpreter/clone-guard.js +18 -0
  53. package/dist/interpreter/collect-tools.d.ts +12 -0
  54. package/dist/interpreter/collect-tools.js +70 -0
  55. package/dist/interpreter/execute-branch.d.ts +3 -0
  56. package/dist/interpreter/execute-branch.js +10 -0
  57. package/dist/interpreter/execute-every.d.ts +15 -0
  58. package/dist/interpreter/execute-every.js +143 -0
  59. package/dist/interpreter/execute-fork.d.ts +3 -0
  60. package/dist/interpreter/execute-fork.js +215 -0
  61. package/dist/interpreter/execute-llm.d.ts +4 -0
  62. package/dist/interpreter/execute-llm.js +217 -0
  63. package/dist/interpreter/execute-loop.d.ts +3 -0
  64. package/dist/interpreter/execute-loop.js +171 -0
  65. package/dist/interpreter/execute-provide.d.ts +10 -0
  66. package/dist/interpreter/execute-provide.js +64 -0
  67. package/dist/interpreter/execute-run.d.ts +3 -0
  68. package/dist/interpreter/execute-run.js +39 -0
  69. package/dist/interpreter/execute-spawn.d.ts +11 -0
  70. package/dist/interpreter/execute-spawn.js +113 -0
  71. package/dist/interpreter/execute-tool.d.ts +5 -0
  72. package/dist/interpreter/execute-tool.js +81 -0
  73. package/dist/interpreter/execute.d.ts +14 -0
  74. package/dist/interpreter/execute.js +119 -0
  75. package/dist/interpreter/framework-cast.d.ts +10 -0
  76. package/dist/interpreter/framework-cast.js +16 -0
  77. package/dist/interpreter/message-helpers.d.ts +9 -0
  78. package/dist/interpreter/message-helpers.js +42 -0
  79. package/dist/interpreter/typeguards.d.ts +25 -0
  80. package/dist/interpreter/typeguards.js +50 -0
  81. package/dist/memory/budget.d.ts +23 -0
  82. package/dist/memory/budget.js +117 -0
  83. package/dist/memory/function-call-utils.d.ts +6 -0
  84. package/dist/memory/function-call-utils.js +25 -0
  85. package/dist/memory/layer-api.d.ts +14 -0
  86. package/dist/memory/layer-api.js +91 -0
  87. package/dist/memory/layer-lifecycle.d.ts +160 -0
  88. package/dist/memory/layer-lifecycle.js +552 -0
  89. package/dist/memory/layer-usage.d.ts +15 -0
  90. package/dist/memory/layer-usage.js +48 -0
  91. package/dist/memory/layers/durable-task-state.d.ts +79 -0
  92. package/dist/memory/layers/durable-task-state.js +118 -0
  93. package/dist/memory/layers/file-reference.d.ts +55 -0
  94. package/dist/memory/layers/file-reference.js +538 -0
  95. package/dist/memory/layers/history-window.d.ts +29 -0
  96. package/dist/memory/layers/history-window.js +114 -0
  97. package/dist/memory/layers/observational-memory.d.ts +53 -0
  98. package/dist/memory/layers/observational-memory.js +110 -0
  99. package/dist/memory/layers/plan.d.ts +55 -0
  100. package/dist/memory/layers/plan.js +369 -0
  101. package/dist/memory/layers/static-content.d.ts +27 -0
  102. package/dist/memory/layers/static-content.js +35 -0
  103. package/dist/memory/layers/steering.d.ts +51 -0
  104. package/dist/memory/layers/steering.js +258 -0
  105. package/dist/memory/layers/tool-memory-layer.d.ts +16 -0
  106. package/dist/memory/layers/tool-memory-layer.js +43 -0
  107. package/dist/memory/layers/working-memory.d.ts +44 -0
  108. package/dist/memory/layers/working-memory.js +92 -0
  109. package/dist/memory/projector.d.ts +10 -0
  110. package/dist/memory/projector.js +19 -0
  111. package/dist/memory/scope.d.ts +3 -0
  112. package/dist/memory/scope.js +31 -0
  113. package/dist/memory/strip-unresolved.d.ts +13 -0
  114. package/dist/memory/strip-unresolved.js +35 -0
  115. package/dist/observability/genai-attributes.d.ts +11 -0
  116. package/dist/observability/genai-attributes.js +12 -0
  117. package/dist/observability/span-impl.d.ts +20 -0
  118. package/dist/observability/span-impl.js +33 -0
  119. package/dist/observability/trace-exporter.d.ts +15 -0
  120. package/dist/observability/trace-exporter.js +26 -0
  121. package/dist/patterns/flow.d.ts +52 -0
  122. package/dist/patterns/flow.js +103 -0
  123. package/dist/patterns/interview.d.ts +53 -0
  124. package/dist/patterns/interview.js +113 -0
  125. package/dist/patterns/plans.d.ts +45 -0
  126. package/dist/patterns/plans.js +125 -0
  127. package/dist/patterns/ralph-wiggum.d.ts +20 -0
  128. package/dist/patterns/ralph-wiggum.js +37 -0
  129. package/dist/patterns/react.d.ts +18 -0
  130. package/dist/patterns/react.js +39 -0
  131. package/dist/portable.d.ts +26 -0
  132. package/dist/portable.js +34 -0
  133. package/dist/runtime/agent-harness.d.ts +206 -0
  134. package/dist/runtime/agent-harness.js +1170 -0
  135. package/dist/runtime/broadcaster-utils.d.ts +30 -0
  136. package/dist/runtime/broadcaster-utils.js +53 -0
  137. package/dist/runtime/channel-store.d.ts +18 -0
  138. package/dist/runtime/channel-store.js +187 -0
  139. package/dist/runtime/context-impl.d.ts +80 -0
  140. package/dist/runtime/context-impl.js +140 -0
  141. package/dist/runtime/cwd-helpers.d.ts +42 -0
  142. package/dist/runtime/cwd-helpers.js +68 -0
  143. package/dist/runtime/detached-handle.d.ts +14 -0
  144. package/dist/runtime/detached-handle.js +56 -0
  145. package/dist/runtime/event-broadcaster.d.ts +61 -0
  146. package/dist/runtime/event-broadcaster.js +187 -0
  147. package/dist/runtime/exec-context-factory.d.ts +25 -0
  148. package/dist/runtime/exec-context-factory.js +30 -0
  149. package/dist/runtime/in-memory-storage.d.ts +9 -0
  150. package/dist/runtime/in-memory-storage.js +33 -0
  151. package/dist/runtime/item-log-impl.d.ts +11 -0
  152. package/dist/runtime/item-log-impl.js +25 -0
  153. package/dist/runtime/message-queue.d.ts +30 -0
  154. package/dist/runtime/message-queue.js +46 -0
  155. package/dist/runtime/session-runner.d.ts +77 -0
  156. package/dist/runtime/session-runner.js +253 -0
  157. package/dist/runtime/session-streams.d.ts +11 -0
  158. package/dist/runtime/session-streams.js +204 -0
  159. package/dist/runtime/tool-memory.d.ts +6 -0
  160. package/dist/runtime/tool-memory.js +22 -0
  161. package/dist/schemas/item.d.ts +35 -0
  162. package/dist/schemas/item.js +155 -0
  163. package/dist/types/channel.d.ts +29 -0
  164. package/dist/types/channel.js +1 -0
  165. package/dist/types/common.d.ts +111 -0
  166. package/dist/types/common.js +1 -0
  167. package/dist/types/context.d.ts +93 -0
  168. package/dist/types/context.js +1 -0
  169. package/dist/types/detached.d.ts +17 -0
  170. package/dist/types/detached.js +7 -0
  171. package/dist/types/embed.d.ts +5 -0
  172. package/dist/types/embed.js +1 -0
  173. package/dist/types/error.d.ts +51 -0
  174. package/dist/types/error.js +1 -0
  175. package/dist/types/fs-adapter.d.ts +51 -0
  176. package/dist/types/fs-adapter.js +3 -0
  177. package/dist/types/harness-result.d.ts +43 -0
  178. package/dist/types/harness-result.js +2 -0
  179. package/dist/types/items.d.ts +115 -0
  180. package/dist/types/items.js +2 -0
  181. package/dist/types/memory.d.ts +336 -0
  182. package/dist/types/memory.js +13 -0
  183. package/dist/types/mutable-context.d.ts +13 -0
  184. package/dist/types/mutable-context.js +1 -0
  185. package/dist/types/observability.d.ts +30 -0
  186. package/dist/types/observability.js +1 -0
  187. package/dist/types/runtime.d.ts +255 -0
  188. package/dist/types/runtime.js +1 -0
  189. package/dist/types/shell-adapter.d.ts +34 -0
  190. package/dist/types/shell-adapter.js +10 -0
  191. package/dist/types/steering.d.ts +89 -0
  192. package/dist/types/steering.js +14 -0
  193. package/dist/types/step.d.ts +206 -0
  194. package/dist/types/step.js +1 -0
  195. package/dist/types/subprocess-adapter.d.ts +41 -0
  196. package/dist/types/subprocess-adapter.js +1 -0
  197. package/dist/types/tool-context.d.ts +34 -0
  198. package/dist/types/tool-context.js +1 -0
  199. package/dist/unstable.d.ts +28 -0
  200. package/dist/unstable.js +24 -0
  201. package/dist/until/combinators.d.ts +17 -0
  202. package/dist/until/combinators.js +49 -0
  203. package/dist/until/never.d.ts +9 -0
  204. package/dist/until/never.js +12 -0
  205. package/dist/until/predicates.d.ts +43 -0
  206. package/dist/until/predicates.js +126 -0
  207. package/package.json +81 -0
@@ -0,0 +1,43 @@
1
+ //#region Helpers
2
+ function toFsStats(s) {
3
+ return {
4
+ size: s.size,
5
+ isDirectory: () => s.isDirectory(),
6
+ isSymbolicLink: () => s.isSymbolicLink(),
7
+ isFile: () => s.isFile(),
8
+ };
9
+ }
10
+ //#endregion
11
+ //#region Public API
12
+ /** Create an FsAdapter backed by the local filesystem via `node:fs/promises`. */
13
+ export function createLocalFsAdapter() {
14
+ const fs = () => import('node:fs/promises');
15
+ const fsConstants = () => import('node:fs');
16
+ return {
17
+ readFile: async (p) => (await fs()).readFile(p),
18
+ readFileText: async (p) => (await fs()).readFile(p, 'utf-8'),
19
+ writeFile: async (p, content) => (await fs()).writeFile(p, content, 'utf-8'),
20
+ writeFileBytes: async (p, content) => (await fs()).writeFile(p, content),
21
+ appendFile: async (p, content) => (await fs()).appendFile(p, content, 'utf-8'),
22
+ mkdir: async (dir) => {
23
+ await (await fs()).mkdir(dir, {
24
+ recursive: true,
25
+ });
26
+ },
27
+ rename: async (oldPath, newPath) => (await fs()).rename(oldPath, newPath),
28
+ rm: async (p, options) => {
29
+ await (await fs()).rm(p, options);
30
+ },
31
+ access: async (p, mode) => {
32
+ const [{ constants }, fsPromises] = await Promise.all([
33
+ fsConstants(),
34
+ fs(),
35
+ ]);
36
+ await fsPromises.access(p, mode ?? constants.R_OK);
37
+ },
38
+ stat: async (p) => toFsStats(await (await fs()).stat(p)),
39
+ lstat: async (p) => toFsStats(await (await fs()).lstat(p)),
40
+ readdir: async (p) => (await fs()).readdir(p),
41
+ };
42
+ }
43
+ //#endregion
@@ -0,0 +1,51 @@
1
+ import type { ShellAdapter } from '../types/shell-adapter';
2
+ /**
3
+ * Options for `createLocalShellAdapter`.
4
+ *
5
+ * @public
6
+ */
7
+ export interface CreateLocalShellAdapterOptions {
8
+ /**
9
+ * When `true`, every command is rewritten through `rtk rewrite`
10
+ * (https://github.com/rtk-ai/rtk) before exec to filter and summarize
11
+ * output for token efficiency. The rewrite is per-command — `rtk` decides
12
+ * which programs it knows how to filter and falls back to the raw command
13
+ * for unknown programs (exit 1, no output → original used).
14
+ *
15
+ * Defaults to `false` so non-CLI embedders keep raw shell semantics.
16
+ * `@noetic/cli` opts in via `createDefaultShellAdapter`.
17
+ *
18
+ * Resolved against `PATH` once at adapter creation; the result is exposed
19
+ * as `rtkAvailable` so callers can fail fast when this is required.
20
+ */
21
+ useRtk?: boolean;
22
+ }
23
+ /**
24
+ * Local shell adapter — a `ShellAdapter` plus introspection for the rtk
25
+ * wrapping decision so the harness factory can verify the binary is present
26
+ * before it starts running tools.
27
+ *
28
+ * @public
29
+ */
30
+ export interface LocalShellAdapter extends ShellAdapter {
31
+ /** Whether `rtk` was found on PATH at adapter creation time. */
32
+ readonly rtkAvailable: boolean;
33
+ /** Resolved absolute path to `rtk`, or `null` when not on PATH. */
34
+ readonly rtkPath: string | null;
35
+ /** Whether wrapping with rtk is enabled (the constructor option). */
36
+ readonly useRtk: boolean;
37
+ }
38
+ /**
39
+ * Create a `ShellAdapter` backed by the local system shell via `Bun.spawn`.
40
+ *
41
+ * Defaults to plain `sh -c` execution. Pass `{ useRtk: true }` to wrap each
42
+ * command through `rtk rewrite` for token efficiency — best-effort: any
43
+ * failure (rtk missing, exit 1, timeout) falls through to the raw command.
44
+ * The CLI opts in via `createDefaultShellAdapter`; other embedders of
45
+ * `@noetic-tools/core` keep raw shell semantics unless they explicitly enable rtk.
46
+ *
47
+ * Call sites that want hard-fail semantics on missing rtk should read
48
+ * `rtkAvailable` on the returned adapter and refuse to boot when it is
49
+ * `false`.
50
+ */
51
+ export declare function createLocalShellAdapter(opts?: CreateLocalShellAdapterOptions): LocalShellAdapter;
@@ -0,0 +1,167 @@
1
+ import { TIMEOUT_ERROR_PREFIX } from '../types/shell-adapter';
2
+ //#endregion
3
+ //#region Helpers
4
+ const RTK_REWRITE_TIMEOUT_MS = 2e3;
5
+ function readStream(stream, onData) {
6
+ const chunks = [];
7
+ const reader = stream.getReader();
8
+ async function pump() {
9
+ while (true) {
10
+ const { done, value } = await reader.read();
11
+ if (done) {
12
+ break;
13
+ }
14
+ const buf = Buffer.from(value);
15
+ chunks.push(buf);
16
+ onData?.(buf);
17
+ }
18
+ return Buffer.concat(chunks).toString('utf-8');
19
+ }
20
+ return pump();
21
+ }
22
+ /**
23
+ * Ask `rtk rewrite` to translate the raw command into its filtered form.
24
+ *
25
+ * Returns the rewritten command string when rtk knows how to wrap it
26
+ * (exit 0, non-empty stdout). Returns `null` on any other outcome — exit 1
27
+ * (rtk doesn't recognize the program), timeout, spawn failure — so the
28
+ * caller falls through to the raw command. This mirrors the documented
29
+ * hook contract: `REWRITTEN=$(rtk rewrite "$CMD") || exit 0`.
30
+ */
31
+ async function rewriteWithRtk(rtkPath, command) {
32
+ try {
33
+ const proc = Bun.spawn([
34
+ rtkPath,
35
+ 'rewrite',
36
+ command,
37
+ ], {
38
+ stdout: 'pipe',
39
+ stderr: 'ignore',
40
+ });
41
+ const timeoutHandle = setTimeout(() => {
42
+ proc.kill();
43
+ }, RTK_REWRITE_TIMEOUT_MS);
44
+ try {
45
+ const [stdout, exitCode] = await Promise.all([
46
+ readStream(proc.stdout),
47
+ proc.exited,
48
+ ]);
49
+ if (exitCode !== 0) {
50
+ return null;
51
+ }
52
+ const trimmed = stdout.trim();
53
+ return trimmed.length > 0 ? trimmed : null;
54
+ }
55
+ finally {
56
+ clearTimeout(timeoutHandle);
57
+ }
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ //#endregion
64
+ //#region Public API
65
+ /**
66
+ * Create a `ShellAdapter` backed by the local system shell via `Bun.spawn`.
67
+ *
68
+ * Defaults to plain `sh -c` execution. Pass `{ useRtk: true }` to wrap each
69
+ * command through `rtk rewrite` for token efficiency — best-effort: any
70
+ * failure (rtk missing, exit 1, timeout) falls through to the raw command.
71
+ * The CLI opts in via `createDefaultShellAdapter`; other embedders of
72
+ * `@noetic-tools/core` keep raw shell semantics unless they explicitly enable rtk.
73
+ *
74
+ * Call sites that want hard-fail semantics on missing rtk should read
75
+ * `rtkAvailable` on the returned adapter and refuse to boot when it is
76
+ * `false`.
77
+ */
78
+ export function createLocalShellAdapter(opts) {
79
+ const useRtk = opts?.useRtk ?? false;
80
+ // Read PATH from process.env so tests can override it. `Bun.which(name)`
81
+ // without an explicit PATH ignores subsequent process.env.PATH mutations.
82
+ const rtkPath = useRtk
83
+ ? (Bun.which('rtk', {
84
+ PATH: process.env.PATH ?? '',
85
+ }) ?? null)
86
+ : null;
87
+ const rtkAvailable = rtkPath !== null;
88
+ async function exec(command, options) {
89
+ let effectiveCommand = command;
90
+ if (useRtk && rtkPath) {
91
+ const rewritten = await rewriteWithRtk(rtkPath, command);
92
+ if (rewritten) {
93
+ effectiveCommand = rewritten;
94
+ }
95
+ }
96
+ const proc = Bun.spawn([
97
+ 'sh',
98
+ '-c',
99
+ effectiveCommand,
100
+ ], {
101
+ cwd: options.cwd,
102
+ env: options.env
103
+ ? {
104
+ ...process.env,
105
+ ...options.env,
106
+ }
107
+ : undefined,
108
+ stdin: options.stdin
109
+ ? new Blob([
110
+ options.stdin,
111
+ ])
112
+ : undefined,
113
+ stdout: 'pipe',
114
+ stderr: 'pipe',
115
+ });
116
+ let timedOut = false;
117
+ let timeoutHandle;
118
+ const timeoutSeconds = options.timeout;
119
+ if (timeoutSeconds !== undefined && timeoutSeconds > 0) {
120
+ timeoutHandle = setTimeout(() => {
121
+ timedOut = true;
122
+ proc.kill();
123
+ }, timeoutSeconds * 1e3);
124
+ }
125
+ const abortHandler = () => {
126
+ proc.kill();
127
+ };
128
+ if (options.signal) {
129
+ options.signal.addEventListener('abort', abortHandler, {
130
+ once: true,
131
+ });
132
+ }
133
+ try {
134
+ const [stdout, stderr] = await Promise.all([
135
+ readStream(proc.stdout, options.onData),
136
+ readStream(proc.stderr, options.onData),
137
+ ]);
138
+ const exitCode = await proc.exited;
139
+ // Only timeout throws. Signal-driven aborts resolve normally so
140
+ // callers can decide via their own state whether the result is
141
+ // a real exit or a cancellation (see runUserShellCommand).
142
+ if (timedOut) {
143
+ throw new Error(`${TIMEOUT_ERROR_PREFIX}${timeoutSeconds}`);
144
+ }
145
+ return {
146
+ stdout,
147
+ stderr,
148
+ exitCode,
149
+ };
150
+ }
151
+ finally {
152
+ if (timeoutHandle) {
153
+ clearTimeout(timeoutHandle);
154
+ }
155
+ if (options.signal) {
156
+ options.signal.removeEventListener('abort', abortHandler);
157
+ }
158
+ }
159
+ }
160
+ return {
161
+ exec,
162
+ rtkAvailable,
163
+ rtkPath,
164
+ useRtk,
165
+ };
166
+ }
167
+ //#endregion
@@ -0,0 +1,16 @@
1
+ import type { ChildProcess, SpawnOptions } from 'node:child_process';
2
+ import type { SubprocessAdapter } from '../types/subprocess-adapter';
3
+ export type SubprocessSignal = 'SIGTERM' | 'SIGSTOP' | 'SIGCONT';
4
+ export interface ProcessSignaller {
5
+ kill(target: number, signal: SubprocessSignal): void;
6
+ isAlive(pid: number): boolean;
7
+ startTime(pid: number): string | null;
8
+ }
9
+ export interface CreateLocalSubprocessAdapterOptions {
10
+ spawnFn?: (command: string, args: ReadonlyArray<string>, options: SpawnOptions) => Pick<ChildProcess, 'pid' | 'unref'> & {
11
+ on(event: 'error', listener: (err: Error) => void): unknown;
12
+ };
13
+ signaller?: ProcessSignaller;
14
+ }
15
+ export declare const defaultProcessSignaller: ProcessSignaller;
16
+ export declare function createLocalSubprocessAdapter(options?: CreateLocalSubprocessAdapterOptions): SubprocessAdapter;
@@ -0,0 +1,187 @@
1
+ import { execFileSync, spawn } from 'node:child_process';
2
+ function isErrnoException(err) {
3
+ return typeof err === 'object' && err !== null && 'code' in err;
4
+ }
5
+ function readPidStartTime(pid) {
6
+ try {
7
+ const out = execFileSync('ps', [
8
+ '-p',
9
+ String(pid),
10
+ '-o',
11
+ 'lstart=',
12
+ ], {
13
+ stdio: [
14
+ 'ignore',
15
+ 'pipe',
16
+ 'ignore',
17
+ ],
18
+ encoding: 'utf8',
19
+ }).trim();
20
+ return out.length === 0 ? null : out;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ export const defaultProcessSignaller = {
27
+ kill(target, signal) {
28
+ process.kill(target, signal);
29
+ },
30
+ isAlive(pid) {
31
+ try {
32
+ process.kill(pid, 0);
33
+ return true;
34
+ }
35
+ catch (err) {
36
+ return isErrnoException(err) && err.code === 'EPERM';
37
+ }
38
+ },
39
+ startTime(pid) {
40
+ return readPidStartTime(pid);
41
+ },
42
+ };
43
+ function nowIso() {
44
+ return new Date().toISOString();
45
+ }
46
+ export function createLocalSubprocessAdapter(options = {}) {
47
+ const signaller = options.signaller ?? defaultProcessSignaller;
48
+ const spawnFn = options.spawnFn ?? ((command, args, opts) => spawn(command, args.slice(), opts));
49
+ const handles = new Map();
50
+ async function save(handle) {
51
+ handles.set(handle.id, handle);
52
+ return handle;
53
+ }
54
+ async function patch(handle, status) {
55
+ return save({
56
+ ...handle,
57
+ status,
58
+ updatedAt: nowIso(),
59
+ });
60
+ }
61
+ function pidFor(handle) {
62
+ return typeof handle.metadata?.pid === 'number' ? handle.metadata.pid : null;
63
+ }
64
+ return {
65
+ async spawn(request) {
66
+ let asyncSpawnError = null;
67
+ const child = spawnFn(request.command, request.args ?? [], {
68
+ cwd: request.cwd,
69
+ detached: request.detached ?? false,
70
+ stdio: 'ignore',
71
+ env: request.env
72
+ ? {
73
+ ...process.env,
74
+ ...request.env,
75
+ }
76
+ : undefined,
77
+ });
78
+ child.on('error', (err) => {
79
+ asyncSpawnError = err;
80
+ });
81
+ child.unref();
82
+ if (child.pid === undefined || !signaller.isAlive(child.pid)) {
83
+ throw new Error(asyncSpawnError instanceof Error
84
+ ? asyncSpawnError.message
85
+ : `Subprocess failed to start: ${request.command}`);
86
+ }
87
+ const now = nowIso();
88
+ return save({
89
+ id: `subprocess-${crypto.randomUUID()}`,
90
+ status: 'running',
91
+ startedAt: now,
92
+ updatedAt: now,
93
+ metadata: {
94
+ ...(request.metadata ?? {}),
95
+ runtime: 'local',
96
+ pid: child.pid,
97
+ pidStarttime: signaller.startTime(child.pid),
98
+ command: request.command,
99
+ args: request.args ?? [],
100
+ cwd: request.cwd,
101
+ detached: request.detached ?? false,
102
+ },
103
+ });
104
+ },
105
+ async get(handleId) {
106
+ return handles.get(handleId) ?? null;
107
+ },
108
+ async stop(handleId, reason) {
109
+ const handle = handles.get(handleId);
110
+ if (!handle) {
111
+ return {
112
+ kind: 'not_found',
113
+ handleId,
114
+ };
115
+ }
116
+ const pid = pidFor(handle);
117
+ if (pid !== null) {
118
+ try {
119
+ signaller.kill(handle.metadata?.detached === true ? -pid : pid, 'SIGTERM');
120
+ }
121
+ catch {
122
+ /* process may already be gone */
123
+ }
124
+ }
125
+ const next = await patch({
126
+ ...handle,
127
+ metadata: {
128
+ ...(handle.metadata ?? {}),
129
+ stopReason: reason,
130
+ },
131
+ }, 'stopped');
132
+ return {
133
+ kind: 'stopped',
134
+ handleId,
135
+ handle: next,
136
+ };
137
+ },
138
+ async pause(handleId) {
139
+ const handle = handles.get(handleId);
140
+ if (!handle) {
141
+ return {
142
+ kind: 'not_found',
143
+ handleId,
144
+ };
145
+ }
146
+ const pid = pidFor(handle);
147
+ if (pid === null) {
148
+ return {
149
+ kind: 'unsupported',
150
+ handle,
151
+ message: 'Subprocess handle has no pid metadata.',
152
+ };
153
+ }
154
+ signaller.kill(handle.metadata?.detached === true ? -pid : pid, 'SIGSTOP');
155
+ return {
156
+ kind: 'ok',
157
+ handle: await patch(handle, 'paused'),
158
+ };
159
+ },
160
+ async resume(handleId) {
161
+ const handle = handles.get(handleId);
162
+ if (!handle) {
163
+ return {
164
+ kind: 'not_found',
165
+ handleId,
166
+ };
167
+ }
168
+ const pid = pidFor(handle);
169
+ if (pid === null) {
170
+ return {
171
+ kind: 'unsupported',
172
+ handle,
173
+ message: 'Subprocess handle has no pid metadata.',
174
+ };
175
+ }
176
+ signaller.kill(handle.metadata?.detached === true ? -pid : pid, 'SIGCONT');
177
+ return {
178
+ kind: 'ok',
179
+ handle: await patch(handle, 'running'),
180
+ };
181
+ },
182
+ async isAlive(handle) {
183
+ const pid = pidFor(handle);
184
+ return pid !== null && signaller.isAlive(pid);
185
+ },
186
+ };
187
+ }
@@ -0,0 +1,57 @@
1
+ import type * as OpenRouterAgent from '@openrouter/agent';
2
+ import type { LLMResponse, Tool } from '../types/common';
3
+ import type { Context } from '../types/context';
4
+ import type { EmbedFn } from '../types/embed';
5
+ import type { Item } from '../types/items';
6
+ import type { MemoryLayer } from '../types/memory';
7
+ import type { AgentHarnessContract } from '../types/runtime';
8
+ type ProviderOutputItem = OpenRouterAgent.OpenResponsesResult['output'][number];
9
+ type OpenRouterInputItem = OpenRouterAgent.EasyInputMessage | OpenRouterAgent.FunctionCallItem | OpenRouterAgent.FunctionCallOutputItem | ProviderOutputItem;
10
+ /** @internal */
11
+ export type SdkTool = OpenRouterAgent.Tool;
12
+ /** @internal */
13
+ export interface ConvertToolsParams {
14
+ tools: ReadonlyArray<Tool>;
15
+ }
16
+ /** @internal Extracts system messages from items and returns them as a single instructions string. */
17
+ export declare function extractSystemInstruction(items: ReadonlyArray<Item>): {
18
+ instructions: string | undefined;
19
+ remaining: Item[];
20
+ };
21
+ /** @internal Converts Noetic Items to OpenRouter SDK input format. */
22
+ export declare function itemsToInput(items: ReadonlyArray<Item>): OpenRouterInputItem[];
23
+ /**
24
+ * @internal Extracts output items from an SDK response, passing them through directly
25
+ * as Open Responses compliant items. Falls back to creating a message from `outputText`
26
+ * when the output array contains no message items.
27
+ */
28
+ export declare function extractOutputItems(response: OpenRouterAgent.OpenResponsesResult): Item[];
29
+ /** @internal */
30
+ export declare function extractUsage(usage: OpenRouterAgent.Usage | null | undefined): LLMResponse['usage'];
31
+ /** @internal */
32
+ export declare function convertTools({ tools }: ConvertToolsParams): SdkTool[];
33
+ /** @internal */
34
+ export interface ExecuteToolCallParams {
35
+ toolName: string;
36
+ args: unknown;
37
+ tools: ReadonlyArray<Tool>;
38
+ context: Context;
39
+ harness: AgentHarnessContract;
40
+ layers?: MemoryLayer[];
41
+ }
42
+ /** @internal Execute a single tool call with steering checks. */
43
+ export declare function executeToolCall(params: ExecuteToolCallParams): Promise<{
44
+ output: string;
45
+ result?: unknown;
46
+ error?: boolean;
47
+ }>;
48
+ /**
49
+ * Creates an `EmbedFn` that calls the OpenRouter embeddings API.
50
+ *
51
+ * @public
52
+ * @param apiKey - OpenRouter API key.
53
+ * @param embeddingModel - Model identifier (default: `'openai/text-embedding-3-small'`).
54
+ * @returns An `EmbedFn` that produces embedding vectors for the given texts.
55
+ */
56
+ export declare function createOpenRouterEmbed(apiKey: string, embeddingModel?: string): EmbedFn;
57
+ export {};