@jini-ai/daemon 0.3.7 → 0.5.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 (159) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +50 -8
  3. package/dist/agent-executor/contracts.d.ts +736 -0
  4. package/dist/agent-executor/contracts.js +28 -0
  5. package/dist/agent-executor/event-translation.d.ts +87 -0
  6. package/dist/agent-executor/event-translation.js +230 -0
  7. package/dist/agent-executor/events.d.ts +190 -0
  8. package/dist/agent-executor/events.js +848 -0
  9. package/dist/agent-executor/handoff.d.ts +18 -0
  10. package/dist/agent-executor/handoff.js +16 -0
  11. package/dist/agent-executor/index.d.ts +98 -0
  12. package/dist/agent-executor/index.js +98 -0
  13. package/dist/agent-executor/launch.d.ts +375 -0
  14. package/dist/agent-executor/launch.js +1001 -0
  15. package/dist/agent-executor/mcp-bridge.d.ts +338 -0
  16. package/dist/agent-executor/mcp-bridge.js +835 -0
  17. package/dist/agent-executor/values.d.ts +5 -0
  18. package/dist/agent-executor/values.js +13 -0
  19. package/dist/agent-executor.d.ts +382 -177
  20. package/dist/agent-executor.js +408 -344
  21. package/dist/close-status.d.ts +11 -8
  22. package/dist/close-status.js +31 -19
  23. package/dist/continuation/continuation-transport.d.ts +3 -1
  24. package/dist/continuation/continuation-transport.js +1 -1
  25. package/dist/continuation/journal.d.ts +12 -5
  26. package/dist/continuation/journal.js +4 -4
  27. package/dist/continuation/run-scoped-context-store.d.ts +13 -4
  28. package/dist/continuation/run-scoped-context-store.js +6 -6
  29. package/dist/continuation/run-start-handler.d.ts +1 -1
  30. package/dist/continuation/run-start-handler.js +3 -16
  31. package/dist/delegated-tool-bridge.d.ts +7 -3
  32. package/dist/delegated-tool-bridge.js +32 -31
  33. package/dist/event-log.d.ts +7 -5
  34. package/dist/event-log.js +12 -11
  35. package/dist/frontend-capability-tools.d.ts +1 -1
  36. package/dist/frontend-capability-tools.js +3 -2
  37. package/dist/frontend-session-registry.d.ts +32 -9
  38. package/dist/frontend-session-registry.js +12 -11
  39. package/dist/http/active-context.d.ts +105 -0
  40. package/dist/http/active-context.js +67 -0
  41. package/dist/http/agents.d.ts +68 -0
  42. package/dist/http/agents.js +12 -0
  43. package/dist/http/attachments.d.ts +618 -0
  44. package/dist/http/attachments.js +1034 -0
  45. package/dist/http/cancel-owned-runs.d.ts +49 -0
  46. package/dist/http/cancel-owned-runs.js +35 -0
  47. package/dist/http/component-catalog.d.ts +84 -0
  48. package/dist/http/component-catalog.js +64 -0
  49. package/dist/http/connectors.d.ts +209 -0
  50. package/dist/http/connectors.js +409 -0
  51. package/dist/http/daemon-status.d.ts +76 -0
  52. package/dist/http/daemon-status.js +55 -0
  53. package/dist/http/db-ops.d.ts +123 -0
  54. package/dist/http/db-ops.js +189 -0
  55. package/dist/http/delegated-tools.d.ts +113 -0
  56. package/dist/http/delegated-tools.js +230 -0
  57. package/dist/http/frontend-control.d.ts +136 -0
  58. package/dist/http/frontend-control.js +79 -0
  59. package/dist/http/frontend-sessions.d.ts +110 -0
  60. package/dist/http/frontend-sessions.js +180 -0
  61. package/dist/http/health.d.ts +95 -0
  62. package/dist/http/health.js +40 -0
  63. package/dist/http/host-tools.d.ts +214 -0
  64. package/dist/http/host-tools.js +329 -0
  65. package/dist/http/index.d.ts +52 -0
  66. package/dist/http/index.js +27 -0
  67. package/dist/http/media.d.ts +146 -0
  68. package/dist/http/media.js +346 -0
  69. package/dist/http/memory.d.ts +265 -0
  70. package/dist/http/memory.js +328 -0
  71. package/dist/http/model-proxy.d.ts +49 -0
  72. package/dist/http/model-proxy.js +642 -0
  73. package/dist/http/remote-run-events.d.ts +70 -0
  74. package/dist/http/remote-run-events.js +224 -0
  75. package/dist/http/research.d.ts +48 -0
  76. package/dist/http/research.js +315 -0
  77. package/dist/http/route-manifest.d.ts +35 -0
  78. package/dist/http/route-manifest.js +103 -0
  79. package/dist/http/routines.d.ts +120 -0
  80. package/dist/http/routines.js +298 -0
  81. package/dist/http/run-credentials.d.ts +73 -0
  82. package/dist/http/run-credentials.js +71 -0
  83. package/dist/http/runs.d.ts +91 -0
  84. package/dist/http/runs.js +221 -0
  85. package/dist/http/terminals.d.ts +138 -0
  86. package/dist/http/terminals.js +325 -0
  87. package/dist/http/tool-catalog.d.ts +78 -0
  88. package/dist/http/tool-catalog.js +66 -0
  89. package/dist/http/workspace-root.d.ts +77 -0
  90. package/dist/http/workspace-root.js +63 -0
  91. package/dist/http/xai.d.ts +90 -0
  92. package/dist/http/xai.js +655 -0
  93. package/dist/image-prompt-delivery.d.ts +13 -3
  94. package/dist/image-prompt-delivery.js +5 -5
  95. package/dist/index.d.ts +556 -4
  96. package/dist/index.js +556 -4
  97. package/dist/legacy-data-migration.d.ts +59 -8
  98. package/dist/legacy-data-migration.js +38 -33
  99. package/dist/read-only-tools.d.ts +130 -0
  100. package/dist/read-only-tools.js +115 -0
  101. package/dist/remote-tool-bridge.d.ts +11 -5
  102. package/dist/remote-tool-bridge.js +15 -15
  103. package/dist/routines/index.d.ts +1 -1
  104. package/dist/routines/index.js +1 -1
  105. package/dist/routines/routine-store.d.ts +34 -12
  106. package/dist/routines/routine-store.js +21 -20
  107. package/dist/routines/schedule.d.ts +48 -10
  108. package/dist/routines/schedule.js +21 -21
  109. package/dist/routines/scheduler.d.ts +32 -11
  110. package/dist/routines/scheduler.js +61 -57
  111. package/dist/routines/types.d.ts +10 -3
  112. package/dist/run/core/result.d.ts +6 -2
  113. package/dist/run/core/result.js +3 -3
  114. package/dist/run/core/retry.d.ts +17 -4
  115. package/dist/run/core/retry.js +7 -11
  116. package/dist/run/diagnostics/diagnostics.d.ts +14 -4
  117. package/dist/run/diagnostics/diagnostics.js +7 -7
  118. package/dist/run/index.d.ts +1 -1
  119. package/dist/run/index.js +1 -1
  120. package/dist/run-credentials.d.ts +138 -0
  121. package/dist/run-credentials.js +152 -0
  122. package/dist/run-lifecycle.d.ts +92 -33
  123. package/dist/run-lifecycle.js +86 -73
  124. package/dist/scheduler.d.ts +8 -0
  125. package/dist/scheduler.js +2 -0
  126. package/dist/session-coordination.d.ts +115 -0
  127. package/dist/session-coordination.js +111 -0
  128. package/dist/store/agent-sessions/errors.d.ts +8 -0
  129. package/dist/store/agent-sessions/errors.js +10 -0
  130. package/dist/store/agent-sessions/index.d.ts +12 -0
  131. package/dist/store/agent-sessions/index.js +19 -0
  132. package/dist/store/agent-sessions/pglite.d.ts +12 -0
  133. package/dist/store/agent-sessions/pglite.js +7 -0
  134. package/dist/store/agent-sessions/ports.d.ts +24 -0
  135. package/dist/store/agent-sessions/ports.js +2 -0
  136. package/dist/store/agent-sessions/postgres.d.ts +12 -0
  137. package/dist/store/agent-sessions/postgres.js +7 -0
  138. package/dist/store/agent-sessions/schema.d.ts +3 -0
  139. package/dist/store/agent-sessions/schema.js +23 -0
  140. package/dist/store/agent-sessions/sql.d.ts +35 -0
  141. package/dist/store/agent-sessions/sql.js +59 -0
  142. package/dist/store/agent-sessions/sqlite.d.ts +117 -0
  143. package/dist/store/agent-sessions/sqlite.js +128 -0
  144. package/dist/store/event-log/sqlite.d.ts +75 -0
  145. package/dist/store/event-log/sqlite.js +230 -0
  146. package/dist/surface-exchanges.d.ts +97 -0
  147. package/dist/surface-exchanges.js +153 -0
  148. package/dist/terminal-session.d.ts +43 -11
  149. package/dist/terminal-session.js +22 -20
  150. package/dist/tokens.js +4 -4
  151. package/dist/tool-audit.d.ts +114 -0
  152. package/dist/tool-audit.js +95 -0
  153. package/dist/tool-executor.d.ts +22 -7
  154. package/dist/tool-executor.js +16 -16
  155. package/dist/tool-result-media.d.ts +3 -1
  156. package/dist/tool-result-media.js +1 -1
  157. package/dist/tool-result-surfaces.d.ts +3 -1
  158. package/dist/tool-result-surfaces.js +1 -1
  159. package/package.json +127 -9
@@ -0,0 +1,265 @@
1
+ /**
2
+ * @module memory
3
+ *
4
+ * Generic memory/notes HTTP surface — thin route wrappers over a
5
+ * frontmatter-backed note store, an extraction-attempt log, and a
6
+ * verify-outcome log, plus a multiplexed SSE change feed. Ported from OD's
7
+ * `apps/daemon/src/routes/memory.ts` (690 lines) — see `archived provenance ledger`'s
8
+ * 2026-07-21 memory-routes section for the full per-route classification.
9
+ *
10
+ * **Deliberately does NOT depend on `@jini-ai/memory`.** That package exists in
11
+ * this repo (`packages/memory/`, `createNoteStore`/`createExtractionLog`/
12
+ * `createVerifyLog` structurally match every collaborator type below field
13
+ * for field). The host supplies the concrete memory implementation rather than
14
+ * making the daemon transport depend on that implementation's release policy.
15
+ * The types below (`MemoryNoteStore`/`MemoryExtractionLog`/`MemoryVerifyLog`) are
16
+ * structural mirrors of `@jini-ai/memory`'s real `NoteStore`/`ExtractionLog`/
17
+ * `VerifyLog` interfaces — a real instance satisfies them with zero adapter
18
+ * code, following the dependency-injection convention established by
19
+ * `daemon-status.ts`/`host-tools.ts`/`db-ops.ts`. The route pack remains replaceable, locked
20
+ * or not. If/when `@jini-ai/memory` is promoted to `"stable"`, a follow-up can
21
+ * replace these local types with direct imports — a mechanical change, not
22
+ * a redesign, since the shapes already match.
23
+ *
24
+ * **Ported (generic, no OD coupling):** the config `enabled` toggle, entry
25
+ * CRUD (`POST /api/memory`, `GET/PUT/DELETE /api/memory/:id`), the tree
26
+ * view and single-node patch, the raw index text, the extraction/
27
+ * verification history lists + clear/remove, and the multiplexed
28
+ * `change`/`extraction`/`verify` SSE feed.
29
+ *
30
+ * **Explicitly NOT ported (OD-PRODUCT or missing-primitive, see
31
+ * `archived provenance ledger` for the full reasoning per route):**
32
+ * - `POST /api/memory/rules/suggest` — OD's canvas/deck-annotation shape.
33
+ * - `POST /api/memory/connectors/suggest` / `.../connectors/extract` — OD's
34
+ * project-scoped connector-mining pipeline.
35
+ * - `POST /api/memory/extract` — the heuristic-regex pre-turn phase and the
36
+ * BYOK-chat-provider-passthrough LLM post-turn phase are both OD-specific
37
+ * composition, per this repo's root `AGENTS.md`'s existing note that
38
+ * `@jini-ai/memory`'s "heuristic-regex... prompt-composition pieces" were
39
+ * "explicitly left un-ported."
40
+ * - `GET /api/memory/system-prompt` — depends on `composeMemoryBody`, which
41
+ * does not exist anywhere in `@jini-ai/memory` yet (same `AGENTS.md` note).
42
+ * - The four extra `MemoryConfigPatch` boolean toggles
43
+ * (`chatExtractionEnabled`/`profileEnabled`/`rewriteEnabled`/
44
+ * `verifyEnabled`) and the whole `extraction` (LLM-provider) config
45
+ * sub-object: `@jini-ai/memory`'s `NoteStoreOptions` is `{enabled: boolean}`
46
+ * only — a real, documented capability gap in the underlying store, not a
47
+ * route-level scoping choice. A host needing those toggles today has to
48
+ * layer its own config storage alongside `NoteStore.writeConfig`.
49
+ */
50
+ import type { Express } from 'express';
51
+ import { type AdapterContext } from '@jini-ai/http-kit';
52
+ export interface MemoryNoteEntrySummary {
53
+ readonly id: string;
54
+ readonly name: string;
55
+ readonly description: string;
56
+ readonly type: string;
57
+ readonly updatedAt: number;
58
+ }
59
+ export interface MemoryNoteEntry extends MemoryNoteEntrySummary {
60
+ readonly body: string;
61
+ }
62
+ export interface MemoryTreeNode {
63
+ readonly id: string;
64
+ readonly parentId: string | null;
65
+ readonly path: string;
66
+ readonly name: string;
67
+ readonly description: string;
68
+ readonly kind: 'folder' | 'entry';
69
+ readonly type: string;
70
+ readonly createdAt: string;
71
+ readonly updatedAt: string;
72
+ readonly childrenCount: number;
73
+ }
74
+ export interface MemoryNoteStoreOptions {
75
+ readonly enabled: boolean;
76
+ }
77
+ /** Minimal event-emitter surface every collaborator's `events` field needs — satisfied structurally by a real `node:events` `EventEmitter`. Its positional methods are a framework ABI, exempt from the object-argument convention. */
78
+ export interface MemoryChangeEmitter {
79
+ on(event: string, listener: (event: unknown) => void): unknown;
80
+ off(event: string, listener: (event: unknown) => void): unknown;
81
+ }
82
+ export interface MemoryNoteStore {
83
+ readonly events: MemoryChangeEmitter;
84
+ dir({ dataDir }: {
85
+ dataDir: string;
86
+ }): string;
87
+ readConfig({ dataDir }: {
88
+ dataDir: string;
89
+ }): Promise<MemoryNoteStoreOptions>;
90
+ writeConfig({ dataDir, patch }: {
91
+ dataDir: string;
92
+ patch: Partial<MemoryNoteStoreOptions>;
93
+ }): Promise<MemoryNoteStoreOptions>;
94
+ readIndex({ dataDir }: {
95
+ dataDir: string;
96
+ }): Promise<string>;
97
+ writeIndex({ dataDir, body }: {
98
+ dataDir: string;
99
+ body: string;
100
+ }): Promise<void>;
101
+ listEntries({ dataDir }: {
102
+ dataDir: string;
103
+ }): Promise<readonly MemoryNoteEntrySummary[]>;
104
+ readEntry({ dataDir, id }: {
105
+ dataDir: string;
106
+ id: string;
107
+ }): Promise<MemoryNoteEntry | null>;
108
+ upsertEntry({ dataDir, input }: {
109
+ dataDir: string;
110
+ input: {
111
+ id?: string;
112
+ name: string;
113
+ description?: string;
114
+ type: string;
115
+ body?: string;
116
+ };
117
+ }): Promise<MemoryNoteEntry>;
118
+ deleteEntry({ dataDir, id }: {
119
+ dataDir: string;
120
+ id: string;
121
+ }): Promise<void>;
122
+ updateTreeNode({ dataDir, id, patch }: {
123
+ dataDir: string;
124
+ id: string;
125
+ patch: {
126
+ name?: string;
127
+ description?: string;
128
+ type?: string;
129
+ body?: string;
130
+ };
131
+ }): Promise<MemoryNoteEntry>;
132
+ buildTree({ dataDir }: {
133
+ dataDir: string;
134
+ }): Promise<readonly MemoryTreeNode[]>;
135
+ }
136
+ export interface MemoryExtractionLog {
137
+ readonly events: MemoryChangeEmitter;
138
+ list(): readonly unknown[];
139
+ remove({ id }: {
140
+ id: string;
141
+ }): number;
142
+ clear(): number;
143
+ }
144
+ export interface MemoryVerifyLog {
145
+ readonly events: MemoryChangeEmitter;
146
+ list(): readonly unknown[];
147
+ remove({ id }: {
148
+ id: string;
149
+ }): number;
150
+ clear(): number;
151
+ }
152
+ export interface MemoryHttpDeps {
153
+ readonly notes: MemoryNoteStore;
154
+ readonly extractions: MemoryExtractionLog;
155
+ readonly verifications: MemoryVerifyLog;
156
+ /** Forwarded verbatim to every `MemoryNoteStore`/log call — the same `dataDir` a host passes to its own `createNoteStore`-backed instance. */
157
+ readonly dataDir: string;
158
+ }
159
+ export interface MemoryOverviewResponse {
160
+ readonly enabled: boolean;
161
+ readonly rootDir: string;
162
+ readonly index: string;
163
+ readonly entries: readonly MemoryNoteEntrySummary[];
164
+ }
165
+ export declare const memoryOverviewRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, MemoryOverviewResponse, MemoryHttpDeps>;
166
+ export interface MemoryTreeResponse {
167
+ readonly enabled: boolean;
168
+ readonly rootDir: string;
169
+ readonly tree: readonly MemoryTreeNode[];
170
+ }
171
+ export declare const memoryTreeRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, MemoryTreeResponse, MemoryHttpDeps>;
172
+ export interface MemoryTreeNodePatch {
173
+ readonly name?: string;
174
+ readonly description?: string;
175
+ readonly type?: string;
176
+ readonly body?: string;
177
+ }
178
+ interface MemoryUpdateTreeNodeInput {
179
+ readonly id: string;
180
+ readonly patch: MemoryTreeNodePatch;
181
+ }
182
+ export interface MemoryUpdateTreeNodeResponse {
183
+ readonly entry: MemoryNoteEntry;
184
+ readonly tree: readonly MemoryTreeNode[];
185
+ }
186
+ export declare const memoryUpdateTreeNodeRoute: import("@jini-ai/http-kit").JsonRouteSpec<MemoryUpdateTreeNodeInput, MemoryUpdateTreeNodeResponse, MemoryHttpDeps>;
187
+ export interface MemoryIndexResponse {
188
+ readonly index: string;
189
+ }
190
+ export declare const memoryWriteIndexRoute: import("@jini-ai/http-kit").JsonRouteSpec<string, MemoryIndexResponse, MemoryHttpDeps>;
191
+ export interface MemoryConfigResponse {
192
+ readonly enabled: boolean;
193
+ }
194
+ export declare const memoryWriteConfigRoute: import("@jini-ai/http-kit").JsonRouteSpec<Partial<MemoryNoteStoreOptions>, MemoryConfigResponse, MemoryHttpDeps>;
195
+ export interface MemoryExtractionsResponse {
196
+ readonly extractions: readonly unknown[];
197
+ }
198
+ export declare const memoryListExtractionsRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, MemoryExtractionsResponse, MemoryHttpDeps>;
199
+ export interface MemoryRemovedResponse {
200
+ readonly removed: number;
201
+ }
202
+ export declare const memoryClearExtractionsRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, MemoryRemovedResponse, MemoryHttpDeps>;
203
+ export declare const memoryRemoveExtractionRoute: import("@jini-ai/http-kit").JsonRouteSpec<string, MemoryRemovedResponse, MemoryHttpDeps>;
204
+ export interface MemoryVerificationsResponse {
205
+ readonly verifications: readonly unknown[];
206
+ }
207
+ export declare const memoryListVerificationsRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, MemoryVerificationsResponse, MemoryHttpDeps>;
208
+ export declare const memoryClearVerificationsRoute: import("@jini-ai/http-kit").JsonRouteSpec<void, MemoryRemovedResponse, MemoryHttpDeps>;
209
+ export declare const memoryRemoveVerificationRoute: import("@jini-ai/http-kit").JsonRouteSpec<string, MemoryRemovedResponse, MemoryHttpDeps>;
210
+ export interface MemoryEntryInput {
211
+ readonly id?: string;
212
+ readonly name: string;
213
+ readonly description?: string;
214
+ readonly type: string;
215
+ readonly body?: string;
216
+ }
217
+ export interface MemoryEntryResponse {
218
+ readonly entry: MemoryNoteEntry;
219
+ }
220
+ export declare const memoryCreateEntryRoute: import("@jini-ai/http-kit").JsonRouteSpec<MemoryEntryInput, MemoryEntryResponse, MemoryHttpDeps>;
221
+ export declare const memoryReadEntryRoute: import("@jini-ai/http-kit").JsonRouteSpec<string, MemoryEntryResponse, MemoryHttpDeps>;
222
+ interface MemoryUpdateEntryInput {
223
+ readonly id: string;
224
+ readonly input: MemoryEntryInput;
225
+ }
226
+ export declare const memoryUpdateEntryRoute: import("@jini-ai/http-kit").JsonRouteSpec<MemoryUpdateEntryInput, MemoryEntryResponse, MemoryHttpDeps>;
227
+ export interface MemoryDeleteEntryResponse {
228
+ readonly ok: true;
229
+ }
230
+ export declare const memoryDeleteEntryRoute: import("@jini-ai/http-kit").JsonRouteSpec<string, MemoryDeleteEntryResponse, MemoryHttpDeps>;
231
+ /**
232
+ * `GET /api/memory/events` — one SSE connection multiplexing three channels
233
+ * (matching OD's own reasoning: "so the browser opens one connection instead
234
+ * of two/three"): `connected` (once, on open), `change` (relayed from
235
+ * `deps.notes.events`), `extraction` (relayed from `deps.extractions.events`,
236
+ * whose underlying event name is `'attempt'` — see `@jini-ai/memory`'s
237
+ * `extraction-log.ts`), and `verify` (relayed from `deps.verifications.events`).
238
+ * Uses `sse.ts`'s generic channel rather than OD's bespoke `createSseResponse`;
239
+ * no Last-Event-ID replay (unlike `runs.ts`) since none of the three
240
+ * underlying emitters buffer history for reconnect — this mirrors the OD
241
+ * origin's own behavior (a live tail only, no replay).
242
+ *
243
+ * Bypasses `mountJsonRoute` (raw `app.get`, for the SSE response shape), so it does not get
244
+ * `requireSameOrigin` for free the way the JSON routes below do — the guard is applied here
245
+ * directly, before the SSE channel is opened, so a cross-origin request never gets a stream.
246
+ */
247
+ export declare function registerMemoryEventStream({ app, deps, adapter }: {
248
+ readonly app: Express;
249
+ readonly deps: MemoryHttpDeps;
250
+ readonly adapter: AdapterContext;
251
+ }, _optional?: Record<string, never>): void;
252
+ /**
253
+ * Mounts every ported memory route. Static sub-resources (`/tree`,
254
+ * `/index`, `/config`, `/events`, `/extractions`, `/verifications`) are
255
+ * mounted BEFORE the `/api/memory/:id` catch-all routes, preserving OD's own
256
+ * ordering discipline ("so an `index`/`config`/`extract` slug can't shadow
257
+ * the real handlers") — Express matches routes in registration order.
258
+ */
259
+ export declare function registerMemoryRoutes({ app, deps, adapter }: {
260
+ readonly app: Express;
261
+ readonly deps: MemoryHttpDeps;
262
+ readonly adapter: AdapterContext;
263
+ }, _optional?: Record<string, never>): void;
264
+ export {};
265
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1,328 @@
1
+ import { createApiError } from '@jini-ai/protocol';
2
+ import { defineJsonRoute, mountJsonRoute } from '@jini-ai/http-kit';
3
+ import { guardSameOrigin } from '@jini-ai/http-kit';
4
+ import { validationError } from '@jini-ai/http-kit';
5
+ import { sendApiError, statusForError } from '@jini-ai/http-kit';
6
+ import { createSseChannel } from '@jini-ai/http-kit';
7
+ import { err, ok } from '@jini-ai/http-kit';
8
+ function isRecord(value) {
9
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
10
+ }
11
+ function errorMessage(error) {
12
+ return error instanceof Error ? error.message : String(error);
13
+ }
14
+ function parseIdParam(input) {
15
+ const id = input.params.id;
16
+ return typeof id === 'string' && id.length > 0 ? ok({ value: id }) : err({ error: validationError({ message: 'id must be a non-empty path parameter' }) });
17
+ }
18
+ export const memoryOverviewRoute = defineJsonRoute({
19
+ method: 'get',
20
+ path: '/api/memory',
21
+ parse: () => ok({ value: undefined }),
22
+ handle: async ({ input: _input, deps }) => {
23
+ const [config, index, entries] = await Promise.all([
24
+ deps.notes.readConfig({ dataDir: deps.dataDir }),
25
+ deps.notes.readIndex({ dataDir: deps.dataDir }),
26
+ deps.notes.listEntries({ dataDir: deps.dataDir }),
27
+ ]);
28
+ return ok({ value: { enabled: config.enabled, rootDir: deps.notes.dir({ dataDir: deps.dataDir }), index, entries } });
29
+ },
30
+ });
31
+ export const memoryTreeRoute = defineJsonRoute({
32
+ method: 'get',
33
+ path: '/api/memory/tree',
34
+ parse: () => ok({ value: undefined }),
35
+ handle: async ({ input: _input, deps }) => {
36
+ const [config, tree] = await Promise.all([deps.notes.readConfig({ dataDir: deps.dataDir }), deps.notes.buildTree({ dataDir: deps.dataDir })]);
37
+ return ok({ value: { enabled: config.enabled, rootDir: deps.notes.dir({ dataDir: deps.dataDir }), tree } });
38
+ },
39
+ });
40
+ function parseUpdateTreeNode(input) {
41
+ const parsedId = parseIdParam(input);
42
+ if (!parsedId.ok)
43
+ return parsedId;
44
+ if (!isRecord(input.body))
45
+ return err({ error: validationError({ message: 'body must be a JSON object' }) });
46
+ const patch = {};
47
+ for (const key of ['name', 'description', 'type', 'body']) {
48
+ const value = input.body[key];
49
+ if (value === undefined)
50
+ continue;
51
+ if (typeof value !== 'string')
52
+ return err({ error: validationError({ message: `${key} must be a string when provided` }) });
53
+ patch[key] = value;
54
+ }
55
+ return ok({ value: { id: parsedId.value, patch } });
56
+ }
57
+ export const memoryUpdateTreeNodeRoute = defineJsonRoute({
58
+ method: 'patch',
59
+ path: '/api/memory/tree/:id',
60
+ parse: parseUpdateTreeNode,
61
+ handle: async ({ input: { id, patch }, deps }) => {
62
+ try {
63
+ const entry = await deps.notes.updateTreeNode({ dataDir: deps.dataDir, id, patch });
64
+ const tree = await deps.notes.buildTree({ dataDir: deps.dataDir });
65
+ return ok({ value: { entry, tree } });
66
+ }
67
+ catch (error) {
68
+ const message = errorMessage(error);
69
+ return err({ error: createApiError({ code: message === 'note not found' ? 'NOT_FOUND' : 'BAD_REQUEST', message }) });
70
+ }
71
+ },
72
+ }, { requireSameOrigin: true });
73
+ /**
74
+ * Requires `index` to be present and a string — it is not defaulted.
75
+ *
76
+ * Coercing anything else to `''` meant a malformed PUT *succeeded* and wrote an empty document over
77
+ * the caller's entire memory index: a misspelled field (`{ notes: "…" }`), a client that sent the
78
+ * value as a number, or a body that failed to serialize all landed on the same destructive path and
79
+ * returned 200. Clearing the index is still supported, because it is a real thing to want — it just
80
+ * has to be asked for explicitly with `{ index: "" }`, which no typo and no serialization accident
81
+ * produces by chance.
82
+ */
83
+ function parseIndexBody(input) {
84
+ if (!isRecord(input.body))
85
+ return err({ error: validationError({ message: 'body must be a JSON object' }) });
86
+ const index = input.body.index;
87
+ if (typeof index !== 'string') {
88
+ return err({
89
+ error: validationError({ message: 'index must be a string — send an explicit empty string to clear it' }, {
90
+ issues: [
91
+ { path: 'index', message: 'required string' },
92
+ ]
93
+ })
94
+ });
95
+ }
96
+ return ok({ value: index });
97
+ }
98
+ export const memoryWriteIndexRoute = defineJsonRoute({
99
+ method: 'put',
100
+ path: '/api/memory/index',
101
+ parse: parseIndexBody,
102
+ handle: async ({ input: index, deps }) => {
103
+ try {
104
+ await deps.notes.writeIndex({ dataDir: deps.dataDir, body: index });
105
+ return ok({ value: { index } });
106
+ }
107
+ catch (error) {
108
+ return err({ error: createApiError({ code: 'BAD_REQUEST', message: errorMessage(error) }) });
109
+ }
110
+ },
111
+ }, { requireSameOrigin: true });
112
+ function parseConfigPatch(input) {
113
+ if (!isRecord(input.body))
114
+ return err({ error: validationError({ message: 'body must be a JSON object' }) });
115
+ if (input.body.enabled === undefined)
116
+ return ok({ value: {} });
117
+ if (typeof input.body.enabled !== 'boolean')
118
+ return err({ error: validationError({ message: 'enabled must be a boolean when provided' }) });
119
+ return ok({ value: { enabled: input.body.enabled } });
120
+ }
121
+ export const memoryWriteConfigRoute = defineJsonRoute({
122
+ method: 'patch',
123
+ path: '/api/memory/config',
124
+ parse: parseConfigPatch,
125
+ handle: async ({ input: patch, deps }) => {
126
+ try {
127
+ const next = await deps.notes.writeConfig({ dataDir: deps.dataDir, patch });
128
+ return ok({ value: { enabled: next.enabled } });
129
+ }
130
+ catch (error) {
131
+ return err({ error: createApiError({ code: 'BAD_REQUEST', message: errorMessage(error) }) });
132
+ }
133
+ },
134
+ }, { requireSameOrigin: true });
135
+ export const memoryListExtractionsRoute = defineJsonRoute({
136
+ method: 'get',
137
+ path: '/api/memory/extractions',
138
+ parse: () => ok({ value: undefined }),
139
+ handle: async ({ input: _input, deps }) => ok({ value: { extractions: deps.extractions.list() } }),
140
+ });
141
+ export const memoryClearExtractionsRoute = defineJsonRoute({
142
+ method: 'delete',
143
+ path: '/api/memory/extractions',
144
+ parse: () => ok({ value: undefined }),
145
+ handle: async ({ input: _input, deps }) => ok({ value: { removed: deps.extractions.clear() } }),
146
+ }, { requireSameOrigin: true });
147
+ export const memoryRemoveExtractionRoute = defineJsonRoute({
148
+ method: 'delete',
149
+ path: '/api/memory/extractions/:id',
150
+ parse: parseIdParam,
151
+ handle: async ({ input: id, deps }) => ok({ value: { removed: deps.extractions.remove({ id }) } }),
152
+ }, { requireSameOrigin: true });
153
+ export const memoryListVerificationsRoute = defineJsonRoute({
154
+ method: 'get',
155
+ path: '/api/memory/verifications',
156
+ parse: () => ok({ value: undefined }),
157
+ handle: async ({ input: _input, deps }) => ok({ value: { verifications: deps.verifications.list() } }),
158
+ });
159
+ export const memoryClearVerificationsRoute = defineJsonRoute({
160
+ method: 'delete',
161
+ path: '/api/memory/verifications',
162
+ parse: () => ok({ value: undefined }),
163
+ handle: async ({ input: _input, deps }) => ok({ value: { removed: deps.verifications.clear() } }),
164
+ }, { requireSameOrigin: true });
165
+ export const memoryRemoveVerificationRoute = defineJsonRoute({
166
+ method: 'delete',
167
+ path: '/api/memory/verifications/:id',
168
+ parse: parseIdParam,
169
+ handle: async ({ input: id, deps }) => ok({ value: { removed: deps.verifications.remove({ id }) } }),
170
+ }, { requireSameOrigin: true });
171
+ function parseEntryInput(input) {
172
+ if (!isRecord(input.body))
173
+ return err({ error: validationError({ message: 'body must be a JSON object' }) });
174
+ const { name, type } = input.body;
175
+ if (typeof name !== 'string' || name.length === 0) {
176
+ return err({ error: validationError({ message: 'name is required' }, { issues: [{ path: 'name', message: 'required non-empty string' }] }) });
177
+ }
178
+ if (typeof type !== 'string' || type.length === 0) {
179
+ return err({ error: validationError({ message: 'type is required' }, { issues: [{ path: 'type', message: 'required non-empty string' }] }) });
180
+ }
181
+ const description = input.body.description;
182
+ if (description !== undefined && typeof description !== 'string') {
183
+ return err({ error: validationError({ message: 'description must be a string when provided' }) });
184
+ }
185
+ const body = input.body.body;
186
+ if (body !== undefined && typeof body !== 'string') {
187
+ return err({ error: validationError({ message: 'body must be a string when provided' }) });
188
+ }
189
+ return ok({
190
+ value: {
191
+ name,
192
+ type,
193
+ ...(description === undefined ? {} : { description }),
194
+ ...(body === undefined ? {} : { body }),
195
+ }
196
+ });
197
+ }
198
+ export const memoryCreateEntryRoute = defineJsonRoute({
199
+ method: 'post',
200
+ path: '/api/memory',
201
+ parse: parseEntryInput,
202
+ handle: async ({ input, deps }) => {
203
+ try {
204
+ const entry = await deps.notes.upsertEntry({ dataDir: deps.dataDir, input });
205
+ return ok({ value: { entry } });
206
+ }
207
+ catch (error) {
208
+ return err({ error: createApiError({ code: 'BAD_REQUEST', message: errorMessage(error) }) });
209
+ }
210
+ },
211
+ }, { requireSameOrigin: true, successStatus: 201 });
212
+ export const memoryReadEntryRoute = defineJsonRoute({
213
+ method: 'get',
214
+ path: '/api/memory/:id',
215
+ parse: parseIdParam,
216
+ handle: async ({ input: id, deps }) => {
217
+ const entry = await deps.notes.readEntry({ dataDir: deps.dataDir, id });
218
+ return entry === null ? err({ error: createApiError({ code: 'NOT_FOUND', message: 'memory not found' }) }) : ok({ value: { entry } });
219
+ },
220
+ });
221
+ function parseUpdateEntry(input) {
222
+ const parsedId = parseIdParam(input);
223
+ if (!parsedId.ok)
224
+ return parsedId;
225
+ const parsedInput = parseEntryInput(input);
226
+ if (!parsedInput.ok)
227
+ return parsedInput;
228
+ return ok({ value: { id: parsedId.value, input: parsedInput.value } });
229
+ }
230
+ export const memoryUpdateEntryRoute = defineJsonRoute({
231
+ method: 'put',
232
+ path: '/api/memory/:id',
233
+ parse: parseUpdateEntry,
234
+ handle: async ({ input: { id, input }, deps }) => {
235
+ try {
236
+ const entry = await deps.notes.upsertEntry({ dataDir: deps.dataDir, input: { ...input, id } });
237
+ return ok({ value: { entry } });
238
+ }
239
+ catch (error) {
240
+ return err({ error: createApiError({ code: 'BAD_REQUEST', message: errorMessage(error) }) });
241
+ }
242
+ },
243
+ }, { requireSameOrigin: true });
244
+ export const memoryDeleteEntryRoute = defineJsonRoute({
245
+ method: 'delete',
246
+ path: '/api/memory/:id',
247
+ parse: parseIdParam,
248
+ handle: async ({ input: id, deps }) => {
249
+ try {
250
+ await deps.notes.deleteEntry({ dataDir: deps.dataDir, id });
251
+ return ok({ value: { ok: true } });
252
+ }
253
+ catch (error) {
254
+ return err({ error: createApiError({ code: 'BAD_REQUEST', message: errorMessage(error) }) });
255
+ }
256
+ },
257
+ }, { requireSameOrigin: true });
258
+ /**
259
+ * `GET /api/memory/events` — one SSE connection multiplexing three channels
260
+ * (matching OD's own reasoning: "so the browser opens one connection instead
261
+ * of two/three"): `connected` (once, on open), `change` (relayed from
262
+ * `deps.notes.events`), `extraction` (relayed from `deps.extractions.events`,
263
+ * whose underlying event name is `'attempt'` — see `@jini-ai/memory`'s
264
+ * `extraction-log.ts`), and `verify` (relayed from `deps.verifications.events`).
265
+ * Uses `sse.ts`'s generic channel rather than OD's bespoke `createSseResponse`;
266
+ * no Last-Event-ID replay (unlike `runs.ts`) since none of the three
267
+ * underlying emitters buffer history for reconnect — this mirrors the OD
268
+ * origin's own behavior (a live tail only, no replay).
269
+ *
270
+ * Bypasses `mountJsonRoute` (raw `app.get`, for the SSE response shape), so it does not get
271
+ * `requireSameOrigin` for free the way the JSON routes below do — the guard is applied here
272
+ * directly, before the SSE channel is opened, so a cross-origin request never gets a stream.
273
+ */
274
+ export function registerMemoryEventStream({ app, deps, adapter }, _optional = {}) {
275
+ app.get('/api/memory/events', (req, res) => {
276
+ const origin = guardSameOrigin({ req, origin: adapter });
277
+ if (!origin.ok) {
278
+ sendApiError({ res, status: statusForError({ error: origin.error }), error: origin.error });
279
+ return;
280
+ }
281
+ let seq = 0;
282
+ const channel = createSseChannel({ res });
283
+ const emit = (kind, data) => {
284
+ channel.enqueue({ event: { opaqueCursor: String(seq++), kind, data } });
285
+ };
286
+ const onChange = (event) => emit('change', event);
287
+ const onExtraction = (event) => emit('extraction', event);
288
+ const onVerify = (event) => emit('verify', event);
289
+ deps.notes.events.on('change', onChange);
290
+ deps.extractions.events.on('attempt', onExtraction);
291
+ deps.verifications.events.on('verify', onVerify);
292
+ channel.onClose({
293
+ callback: () => {
294
+ deps.notes.events.off('change', onChange);
295
+ deps.extractions.events.off('attempt', onExtraction);
296
+ deps.verifications.events.off('verify', onVerify);
297
+ }
298
+ });
299
+ channel.open();
300
+ emit('connected', { at: Date.now() });
301
+ });
302
+ }
303
+ /**
304
+ * Mounts every ported memory route. Static sub-resources (`/tree`,
305
+ * `/index`, `/config`, `/events`, `/extractions`, `/verifications`) are
306
+ * mounted BEFORE the `/api/memory/:id` catch-all routes, preserving OD's own
307
+ * ordering discipline ("so an `index`/`config`/`extract` slug can't shadow
308
+ * the real handlers") — Express matches routes in registration order.
309
+ */
310
+ export function registerMemoryRoutes({ app, deps, adapter }, _optional = {}) {
311
+ mountJsonRoute({ app, spec: memoryOverviewRoute, deps, adapter });
312
+ mountJsonRoute({ app, spec: memoryTreeRoute, deps, adapter });
313
+ mountJsonRoute({ app, spec: memoryUpdateTreeNodeRoute, deps, adapter });
314
+ mountJsonRoute({ app, spec: memoryWriteIndexRoute, deps, adapter });
315
+ mountJsonRoute({ app, spec: memoryWriteConfigRoute, deps, adapter });
316
+ registerMemoryEventStream({ app, deps, adapter });
317
+ mountJsonRoute({ app, spec: memoryListExtractionsRoute, deps, adapter });
318
+ mountJsonRoute({ app, spec: memoryClearExtractionsRoute, deps, adapter });
319
+ mountJsonRoute({ app, spec: memoryRemoveExtractionRoute, deps, adapter });
320
+ mountJsonRoute({ app, spec: memoryListVerificationsRoute, deps, adapter });
321
+ mountJsonRoute({ app, spec: memoryClearVerificationsRoute, deps, adapter });
322
+ mountJsonRoute({ app, spec: memoryRemoveVerificationRoute, deps, adapter });
323
+ mountJsonRoute({ app, spec: memoryCreateEntryRoute, deps, adapter });
324
+ mountJsonRoute({ app, spec: memoryReadEntryRoute, deps, adapter });
325
+ mountJsonRoute({ app, spec: memoryUpdateEntryRoute, deps, adapter });
326
+ mountJsonRoute({ app, spec: memoryDeleteEntryRoute, deps, adapter });
327
+ }
328
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1,49 @@
1
+ import type { Express } from 'express';
2
+ import { type AnthropicToolExecutor, type AzureToolExecutor, type GoogleToolExecutor, type OllamaToolExecutor, type OpenAiToolExecutor, type DnsLookupFn, type PinnedFetch } from '@jini-ai/agent-runtime';
3
+ import type { AdapterContext } from '@jini-ai/http-kit';
4
+ /** Diagnostic detail for an internal-error response this route deliberately does not disclose to the client (SEC-005, matching `runs.ts#RunInternalErrorContext`). Only reached if a turn-runner's promise itself rejects — every turn-runner already converts every provider/network failure into an `'error'`+`'end'` event pair internally, so this is a genuinely unexpected-exception path (e.g. a caller-supplied `executeTool` throwing). */
5
+ export interface ModelProxyInternalErrorContext {
6
+ readonly provider: 'anthropic' | 'openai' | 'azure' | 'google' | 'ollama';
7
+ readonly correlationId: string;
8
+ readonly error: unknown;
9
+ }
10
+ export interface ModelProxyHttpDeps {
11
+ /** Host-owned tool execution for the Anthropic proxy route. Omit to stream `tool_use` events without running a server-side tool loop (see module doc). */
12
+ readonly anthropicExecuteTool?: AnthropicToolExecutor;
13
+ /** Host-owned tool execution for the OpenAI proxy route. Same default-unset behavior as `anthropicExecuteTool`. */
14
+ readonly openaiExecuteTool?: OpenAiToolExecutor;
15
+ /** Host-owned tool execution for the Azure OpenAI proxy route. Same default-unset behavior as `anthropicExecuteTool`. */
16
+ readonly azureExecuteTool?: AzureToolExecutor;
17
+ /** Host-owned tool execution for the Google (Gemini) proxy route. Same default-unset behavior as `anthropicExecuteTool`. */
18
+ readonly googleExecuteTool?: GoogleToolExecutor;
19
+ /** Host-owned tool execution for the Ollama proxy route. Same default-unset behavior as `anthropicExecuteTool`. */
20
+ readonly ollamaExecuteTool?: OllamaToolExecutor;
21
+ /** Host-owned sink for the real exception behind a generic internal-error SSE event (SEC-005). Defaults to `console.error`. */
22
+ readonly onInternalError?: (context: ModelProxyInternalErrorContext) => void;
23
+ /**
24
+ * Injectable HTTP transport, forwarded verbatim to every provider turn-runner as its own
25
+ * `fetchImpl` option (`@jini-ai/agent-runtime`'s `AnthropicTurnOptions.fetchImpl`, etc.), which
26
+ * itself defaults to that package's SSRF-guarded, DNS-pinned `pinnedFetch`. This is the
27
+ * dependency-injection seam `__tests__/model-proxy.test.ts` uses to supply a fake transport
28
+ * directly, instead of module-mocking `@jini-ai/agent-runtime`'s `connection-guard.ts` (or, worse,
29
+ * its compiled `dist/` output, since this package only ever imports that package's single public
30
+ * barrel). Defaults to unset, i.e. every route keeps using the real, guarded transport — **never
31
+ * set this in production wiring**; doing so bypasses the SSRF guard entirely for whatever
32
+ * transport is supplied.
33
+ */
34
+ readonly fetchImpl?: PinnedFetch;
35
+ /**
36
+ * Injectable DNS resolver, forwarded verbatim to every provider turn-runner as its own
37
+ * `dnsLookup` option, which itself defaults to `@jini-ai/agent-runtime`'s `node:dns`-backed
38
+ * `defaultDnsLookup`. Same test-only override rationale as `fetchImpl` — pairs with it so a test
39
+ * never needs to mock `node:dns` either.
40
+ */
41
+ readonly dnsLookup?: DnsLookupFn;
42
+ }
43
+ /** Mounts `POST /api/proxy/{anthropic,openai,azure,google,ollama}/stream` and the generic `POST /api/proxy/:provider/stream` catch-all. */
44
+ export declare function registerModelProxyRoutes({ app, deps, adapter }: {
45
+ readonly app: Express;
46
+ readonly deps: ModelProxyHttpDeps;
47
+ readonly adapter: AdapterContext;
48
+ }, _optional?: Record<string, never>): void;
49
+ //# sourceMappingURL=model-proxy.d.ts.map