opencode-codex-memory 0.6.5 → 0.7.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 (41) hide show
  1. package/README.md +26 -28
  2. package/dist/opencode.json +1 -1
  3. package/dist/src/citation.d.ts +9 -0
  4. package/dist/src/citation.js +68 -11
  5. package/dist/src/db.js +10 -0
  6. package/dist/src/host-client.d.ts +1 -0
  7. package/dist/src/host-client.js +1 -0
  8. package/dist/src/index.d.ts +12 -2
  9. package/dist/src/index.js +32 -5
  10. package/dist/src/llm.d.ts +6 -0
  11. package/dist/src/llm.js +21 -10
  12. package/dist/src/phase2.d.ts +2 -0
  13. package/dist/src/phase2.js +1 -1
  14. package/dist/src/rollout-input.d.ts +6 -0
  15. package/dist/src/rollout-input.js +111 -0
  16. package/dist/src/store.d.ts +17 -1
  17. package/dist/src/store.js +90 -4
  18. package/dist/src/v2/agents.d.ts +53 -0
  19. package/dist/src/v2/agents.js +204 -0
  20. package/dist/src/v2/citation-overlay.d.ts +7 -0
  21. package/dist/src/v2/citation-overlay.js +52 -0
  22. package/dist/src/v2/index.d.ts +7 -0
  23. package/dist/src/v2/index.js +10 -0
  24. package/dist/src/v2/injection.d.ts +14 -0
  25. package/dist/src/v2/injection.js +19 -0
  26. package/dist/src/v2/plugin.d.ts +7 -0
  27. package/dist/src/v2/plugin.js +482 -0
  28. package/dist/src/v2/service.d.ts +74 -0
  29. package/dist/src/v2/service.js +173 -0
  30. package/dist/src/v2/shim.d.ts +47 -0
  31. package/dist/src/v2/shim.js +581 -0
  32. package/dist/src/v2/status-rpc.d.ts +197 -0
  33. package/dist/src/v2/status-rpc.js +159 -0
  34. package/dist/src/v2/status.d.ts +3 -0
  35. package/dist/src/v2/status.js +83 -0
  36. package/dist/src/v2/tools.d.ts +33 -0
  37. package/dist/src/v2/tools.js +57 -0
  38. package/dist/src/v2/tui.d.ts +3 -0
  39. package/dist/src/v2/tui.js +750 -0
  40. package/opencode.json +1 -1
  41. package/package.json +38 -2
@@ -0,0 +1,581 @@
1
+ import { memoryRoot } from "../paths.js";
2
+ import { invalidateOwnService, ownServiceClient } from "./service.js";
3
+ let v2ctx = null;
4
+ export function setV2Context(ctx) {
5
+ v2ctx = ctx;
6
+ }
7
+ function ctx() {
8
+ if (!v2ctx)
9
+ throw new Error("v2 context not initialized");
10
+ return v2ctx;
11
+ }
12
+ /** Unwrap {data} vs direct payloads (client shape varies by call). */
13
+ function und(v) {
14
+ const r = v;
15
+ if (r && typeof r === "object" && "data" in r)
16
+ return r.data;
17
+ return v;
18
+ }
19
+ function isNotFoundError(e) {
20
+ if (!e || typeof e !== "object")
21
+ return String(e ?? "").includes("404");
22
+ const r = e;
23
+ if (r.status === 404)
24
+ return true;
25
+ for (const f of [r._tag, r.name, r.message]) {
26
+ if (typeof f === "string" && /notfound|404/i.test(f))
27
+ return true;
28
+ }
29
+ return false;
30
+ }
31
+ async function sessionGoneOnService(client, sessionID) {
32
+ if (typeof client.session.get !== "function")
33
+ return false;
34
+ try {
35
+ const info = await client.session.get({ sessionID });
36
+ if (info?.error && isNotFoundError(info.error)) {
37
+ return true;
38
+ }
39
+ const data = und(info);
40
+ return !data || typeof data !== "object";
41
+ }
42
+ catch (e) {
43
+ return isNotFoundError(e);
44
+ }
45
+ }
46
+ /** Sub-session ids released via delete(): report 404 from get(). */
47
+ const releasedSubSessions = new Set();
48
+ const RELEASED_CAP = 500;
49
+ function markReleased(id) {
50
+ releasedSubSessions.add(id);
51
+ if (releasedSubSessions.size > RELEASED_CAP) {
52
+ const oldest = releasedSubSessions.values().next().value;
53
+ if (oldest !== undefined)
54
+ releasedSubSessions.delete(oldest);
55
+ }
56
+ }
57
+ export function isReleasedSubSession(id) {
58
+ return releasedSubSessions.has(id);
59
+ }
60
+ /** Stable synthetic id for extraction helpers (see create below). */
61
+ export const EXTRACT_STUB_SESSION_ID = "codex-memory-extract-stub";
62
+ /** Test seam. */
63
+ export function resetV2ShimStateForTest() {
64
+ releasedSubSessions.clear();
65
+ invalidateOwnService();
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // Shape adapters
69
+ // ---------------------------------------------------------------------------
70
+ function joinTextParts(content) {
71
+ if (!Array.isArray(content))
72
+ return "";
73
+ const out = [];
74
+ for (const p of content) {
75
+ if (p && typeof p === "object" && p.type === "text" && typeof p.text === "string") {
76
+ out.push(p.text);
77
+ }
78
+ else if (typeof p === "string") {
79
+ out.push(p);
80
+ }
81
+ }
82
+ return out.join("\n");
83
+ }
84
+ /**
85
+ * V2 public transcript messages → V1 session.messages rows
86
+ * ({info:{role}, parts:[...]}) consumed by capture.ts extractText.
87
+ */
88
+ export function adaptV2Messages(msgs) {
89
+ if (!Array.isArray(msgs))
90
+ return [];
91
+ const rows = [];
92
+ for (const m of msgs) {
93
+ if (!m || typeof m !== "object")
94
+ continue;
95
+ if (m.type === "user") {
96
+ rows.push({ info: { role: "user" }, parts: [{ type: "text", text: m.text }] });
97
+ continue;
98
+ }
99
+ if (m.type === "system") {
100
+ rows.push({ info: { role: "system" }, parts: [{ type: "system", text: m.text }] });
101
+ continue;
102
+ }
103
+ if (m.type === "assistant") {
104
+ const parts = [];
105
+ for (const p of m.content ?? []) {
106
+ if (!p || typeof p !== "object")
107
+ continue;
108
+ if (p.type === "text")
109
+ parts.push({ type: "text", text: p.text });
110
+ else if (p.type === "reasoning")
111
+ parts.push({ type: "reasoning" });
112
+ else if (p.type === "tool") {
113
+ // The codemode `execute` wrapper runs code that calls the real
114
+ // tools; expand its toolCalls so the transcript keeps V1's
115
+ // per-tool granularity ([tool: name] input/output).
116
+ const calls = p.state?.metadata?.toolCalls ?? p?.metadata?.toolCalls;
117
+ if (p.name === "execute" && Array.isArray(calls) && calls.length > 0) {
118
+ const outputText = joinTextParts(p.state?.content);
119
+ for (const c of calls) {
120
+ parts.push({
121
+ type: "tool",
122
+ tool: c.tool ?? "execute",
123
+ state: {
124
+ input: c.input ?? p.state?.input,
125
+ ...(outputText ? { output: outputText } : {}),
126
+ },
127
+ });
128
+ }
129
+ }
130
+ else {
131
+ const st = p.state ?? {};
132
+ const outputText = joinTextParts(st.content);
133
+ parts.push({
134
+ type: "tool",
135
+ tool: p.name ?? "unknown",
136
+ state: {
137
+ ...(st.input !== undefined ? { input: st.input } : {}),
138
+ ...(outputText ? { output: outputText } : {}),
139
+ ...(typeof st.error === "string" ? { error: st.error } : {}),
140
+ },
141
+ });
142
+ }
143
+ }
144
+ }
145
+ rows.push({ info: { role: "assistant" }, parts });
146
+ continue;
147
+ }
148
+ // synthetic/skill/shell/compaction/…: keep visible text, if any.
149
+ if (typeof m.text === "string") {
150
+ rows.push({ info: { role: m.type }, parts: [{ type: "text", text: m.text }] });
151
+ }
152
+ else {
153
+ rows.push({ info: { role: m.type }, parts: [] });
154
+ }
155
+ }
156
+ return rows;
157
+ }
158
+ /** V2 catalog.model.list → V1 provider-list shape for catalogVariantKeys. */
159
+ export function adaptProviderCatalog(v2) {
160
+ const items = und(v2);
161
+ const list = Array.isArray(items) ? items : items?.data ?? [];
162
+ const providers = new Map();
163
+ for (const m of list) {
164
+ if (!m || typeof m !== "object")
165
+ continue;
166
+ const providerID = m.providerID;
167
+ const modelID = m.modelID ?? m.id;
168
+ if (typeof providerID !== "string" || typeof modelID !== "string")
169
+ continue;
170
+ if (!providers.has(providerID))
171
+ providers.set(providerID, {});
172
+ const variants = {};
173
+ for (const v of m.variants ?? []) {
174
+ if (v && typeof v === "object" && typeof v.id === "string") {
175
+ variants[v.id] = { ...(typeof v.disabled === "boolean" ? { disabled: v.disabled } : {}) };
176
+ }
177
+ }
178
+ ;
179
+ providers.get(providerID)[modelID] = { variants };
180
+ }
181
+ return { all: [...providers.entries()].map(([id, models]) => ({ id, models })) };
182
+ }
183
+ /** V2 mcp.list → V1 mcp.status map shape. */
184
+ export function adaptMcpStatus(v2) {
185
+ const items = und(v2);
186
+ const list = Array.isArray(items) ? items : [];
187
+ const out = {};
188
+ for (const s of list) {
189
+ if (!s || typeof s !== "object" || typeof s.name !== "string")
190
+ continue;
191
+ const st = s.status;
192
+ out[s.name] = { status: typeof st === "string" ? st : (st?.status ?? "unknown") };
193
+ }
194
+ return out;
195
+ }
196
+ function parseModelRef(ref) {
197
+ const slash = ref.indexOf("/");
198
+ if (slash <= 0 || slash === ref.length - 1)
199
+ return null;
200
+ return { providerID: ref.slice(0, slash), modelID: ref.slice(slash + 1) };
201
+ }
202
+ function responseRows(response) {
203
+ const payload = und(response);
204
+ if (Array.isArray(payload))
205
+ return payload;
206
+ if (payload && typeof payload === "object" && Array.isArray(payload.data)) {
207
+ return payload.data;
208
+ }
209
+ return null;
210
+ }
211
+ function responseNextCursor(response) {
212
+ const payload = und(response);
213
+ const direct = response?.cursor?.next;
214
+ const next = direct ?? payload?.cursor?.next;
215
+ return typeof next === "string" && next.length > 0 ? next : undefined;
216
+ }
217
+ function adaptV2SessionRow(row) {
218
+ if (!row || typeof row !== "object")
219
+ return row;
220
+ const record = row;
221
+ const location = record.location;
222
+ const directory = typeof record.directory === "string"
223
+ ? record.directory
224
+ : location && typeof location === "object" && typeof location.directory === "string"
225
+ ? location.directory
226
+ : undefined;
227
+ return directory === undefined ? row : { ...record, directory };
228
+ }
229
+ // ---------------------------------------------------------------------------
230
+ // The façade: V1-shaped client over the V2 context
231
+ // ---------------------------------------------------------------------------
232
+ async function v2promptWithWait(sessionID, body, signal) {
233
+ const c = ctx();
234
+ const text = body.parts.map((p) => p.text ?? "").join("\n");
235
+ // Structured-output extraction: V2 prompts carry text only, so run the
236
+ // turn through generate.text (inherently tool-less, like the V1
237
+ // memorize-extract sandbox) with the system prompt prepended. The caller
238
+ // falls back to JSON text parsing (hostStructuredOutput finds nothing).
239
+ if (body.format) {
240
+ const prompt = body.system ? `${body.system}\n\n---\n\n${text}` : text;
241
+ const parsed = body.model ? parseModelRef(`${body.model.providerID}/${body.model.modelID}`) : null;
242
+ const payload = {
243
+ prompt,
244
+ ...(parsed || body.variant
245
+ ? {
246
+ model: {
247
+ ...(parsed ? { providerID: parsed.providerID, id: parsed.modelID } : {}),
248
+ ...(body.variant ? { variant: body.variant } : {}),
249
+ },
250
+ }
251
+ : {}),
252
+ };
253
+ if (signal?.aborted)
254
+ throw new Error("sub-agent prompt cancelled");
255
+ const publicClient = await ownServiceClient();
256
+ if (typeof publicClient?.generate?.text === "function") {
257
+ const gen = await publicClient.generate.text(payload, signal ? { signal } : undefined);
258
+ const outText = typeof gen?.text === "string" ? gen.text : JSON.stringify(gen);
259
+ return { data: { parts: [{ type: "text", text: outText }] } };
260
+ }
261
+ // ctx.generate.text ignores request-option signals. Race AbortSignal.
262
+ const genP = c.generate.text(payload);
263
+ const gen = signal
264
+ ? await Promise.race([
265
+ genP,
266
+ new Promise((_, reject) => {
267
+ signal.addEventListener("abort", () => reject(new Error("sub-agent prompt cancelled")), { once: true });
268
+ }),
269
+ ])
270
+ : await genP;
271
+ const outText = typeof gen?.text === "string" ? gen.text : JSON.stringify(gen);
272
+ return { data: { parts: [{ type: "text", text: outText }] } };
273
+ }
274
+ // Agentic turn (consolidation): the agent/model must be set at CREATE time
275
+ // in V2, so switch the fresh helper session first, then prompt + wait to
276
+ // preserve V1's "prompt resolves after the turn" semantics.
277
+ if (body.agent) {
278
+ await c.session.switchAgent({ sessionID, agent: body.agent });
279
+ }
280
+ if (body.model) {
281
+ await c.session.switchModel({
282
+ sessionID,
283
+ model: {
284
+ providerID: body.model.providerID,
285
+ id: body.model.modelID,
286
+ ...(body.variant ? { variant: body.variant } : {}),
287
+ },
288
+ });
289
+ }
290
+ const posted = await c.session.prompt({ sessionID, text });
291
+ const waitP = c.session.wait({ sessionID });
292
+ if (signal) {
293
+ if (signal.aborted) {
294
+ await c.session.interrupt({ sessionID }).catch(() => { });
295
+ await waitP.catch(() => { });
296
+ throw new Error("sub-agent prompt cancelled");
297
+ }
298
+ await Promise.race([
299
+ waitP,
300
+ new Promise((_, reject) => {
301
+ signal.addEventListener("abort", () => reject(new Error("sub-agent prompt cancelled")), { once: true });
302
+ }),
303
+ ]).catch(async (e) => {
304
+ await c.session.interrupt({ sessionID }).catch(() => { });
305
+ await waitP.catch(() => { });
306
+ throw e;
307
+ });
308
+ }
309
+ else {
310
+ await waitP;
311
+ }
312
+ return { data: { posted } };
313
+ }
314
+ /** Build the V1-shaped client. Passed to setPluginInput() by V2 setup(). */
315
+ export function buildV1ClientShim() {
316
+ async function serviceOrThrow() {
317
+ const client = await ownServiceClient();
318
+ if (!client)
319
+ throw new Error("no healthy registered OpenCode 2 service for global memory operations");
320
+ return client;
321
+ }
322
+ async function listGlobalSessions(limit, cursor, search) {
323
+ const client = await serviceOrThrow();
324
+ const pageSize = Math.min(Math.max(limit, 1), 5000);
325
+ const out = [];
326
+ const seenCursors = new Set();
327
+ const timestampCursor = typeof cursor === "number" ? cursor : undefined;
328
+ let next = typeof cursor === "string" ? cursor : undefined;
329
+ while (out.length < limit) {
330
+ const response = (await client.session.list?.({
331
+ limit: pageSize,
332
+ order: "desc",
333
+ parentID: null,
334
+ ...(search ? { search } : {}),
335
+ ...(next ? { cursor: next } : {}),
336
+ }));
337
+ const rawRows = responseRows(response);
338
+ if (!rawRows)
339
+ throw new Error("registered service returned an invalid session list");
340
+ const rows = rawRows
341
+ .map(adaptV2SessionRow)
342
+ .filter((row) => {
343
+ if (!row || typeof row !== "object")
344
+ return false;
345
+ const record = row;
346
+ const time = record.time;
347
+ const updated = time && typeof time === "object" ? time.updated : undefined;
348
+ if (timestampCursor !== undefined && (typeof updated !== "number" || updated >= timestampCursor))
349
+ return false;
350
+ if (!search)
351
+ return true;
352
+ const title = typeof record.title === "string" ? record.title : "";
353
+ const id = typeof record.id === "string" ? record.id : "";
354
+ return `${id}\n${title}`.toLowerCase().includes(search.toLowerCase());
355
+ });
356
+ out.push(...rows);
357
+ const candidate = responseNextCursor(response);
358
+ if (!candidate || seenCursors.has(candidate) || rawRows.length === 0)
359
+ break;
360
+ seenCursors.add(candidate);
361
+ next = candidate;
362
+ }
363
+ return { data: out.slice(0, limit) };
364
+ }
365
+ const session = {
366
+ create: async (opts) => {
367
+ try {
368
+ // Extraction turns run through generate.text (see prompt below) and
369
+ // never touch a session, so hand out a stable synthetic id instead
370
+ // of creating a server row per extraction (without this each
371
+ // extraction would litter one dead
372
+ // `codex-memory-extract-*` session). Skip/tracking logic keys off
373
+ // the id string only, so behavior is unchanged.
374
+ if (opts?.body?.title?.startsWith("codex-memory-extract-")) {
375
+ releasedSubSessions.delete(EXTRACT_STUB_SESSION_ID);
376
+ return { data: { id: EXTRACT_STUB_SESSION_ID } };
377
+ }
378
+ const res = await ctx().session.create({
379
+ ...(opts?.body?.title ? { title: opts.body.title } : {}),
380
+ ...(opts?.body?.metadata ? { metadata: opts.body.metadata } : {}),
381
+ location: { directory: opts?.query?.directory ?? memoryRoot() },
382
+ });
383
+ return { data: { id: und(res)?.id } };
384
+ }
385
+ catch (e) {
386
+ return { error: e };
387
+ }
388
+ },
389
+ prompt: async (opts) => {
390
+ try {
391
+ return await v2promptWithWait(opts.path.id, opts.body, opts.signal);
392
+ }
393
+ catch (e) {
394
+ return { error: e };
395
+ }
396
+ },
397
+ messages: async (opts) => {
398
+ try {
399
+ if (isReleasedSubSession(opts.path.id))
400
+ throw Object.assign(new Error("SessionNotFound"), { _tag: "SessionNotFoundError" });
401
+ const client = await serviceOrThrow();
402
+ if (typeof client.message?.list !== "function")
403
+ throw new Error("registered service does not support message.list");
404
+ const messages = [];
405
+ const seenCursors = new Set();
406
+ let cursor;
407
+ while (true) {
408
+ const response = await client.message.list(cursor ? { sessionID: opts.path.id, cursor } : { sessionID: opts.path.id, order: "asc" });
409
+ const rows = responseRows(response);
410
+ if (!rows)
411
+ throw new Error("registered service returned an invalid message list");
412
+ messages.push(...rows);
413
+ const next = responseNextCursor(response);
414
+ if (!next || seenCursors.has(next))
415
+ break;
416
+ seenCursors.add(next);
417
+ cursor = next;
418
+ }
419
+ return { data: adaptV2Messages(messages) };
420
+ }
421
+ catch (e) {
422
+ return { error: e };
423
+ }
424
+ },
425
+ delete: async (opts) => {
426
+ let shutdownError;
427
+ try {
428
+ const client = await serviceOrThrow();
429
+ if (typeof client.session.remove !== "function")
430
+ throw new Error("registered service does not support session.remove");
431
+ const result = await client.session.remove({ sessionID: opts.path.id }, opts.signal ? { signal: opts.signal } : undefined);
432
+ if (result?.error)
433
+ throw result.error;
434
+ if (await sessionGoneOnService(client, opts.path.id)) {
435
+ markReleased(opts.path.id);
436
+ return {};
437
+ }
438
+ throw new Error("session still exists after remove");
439
+ }
440
+ catch (error) {
441
+ shutdownError = error;
442
+ }
443
+ try {
444
+ const session = ctx().session;
445
+ const interrupt = await session.interrupt({ sessionID: opts.path.id });
446
+ if (interrupt?.error)
447
+ throw interrupt.error;
448
+ if (typeof session.wait === "function")
449
+ await session.wait({ sessionID: opts.path.id });
450
+ }
451
+ catch (interruptError) {
452
+ return { error: shutdownError ?? interruptError };
453
+ }
454
+ try {
455
+ const client = await ownServiceClient();
456
+ if (!client?.session?.get)
457
+ return { error: shutdownError ?? new Error("session still exists after interrupt") };
458
+ const info = await client.session.get({ sessionID: opts.path.id });
459
+ if (info?.error && isNotFoundError(info.error)) {
460
+ markReleased(opts.path.id);
461
+ return {};
462
+ }
463
+ const data = und(info);
464
+ if (data && typeof data === "object") {
465
+ return { error: shutdownError ?? new Error("session still exists after interrupt") };
466
+ }
467
+ return { error: shutdownError ?? new Error("session still exists after interrupt") };
468
+ }
469
+ catch (e) {
470
+ if (isNotFoundError(e)) {
471
+ markReleased(opts.path.id);
472
+ return {};
473
+ }
474
+ return { error: shutdownError ?? e };
475
+ }
476
+ },
477
+ get: async (opts) => {
478
+ try {
479
+ if (isReleasedSubSession(opts.path.id)) {
480
+ return { response: { status: 404 }, error: { _tag: "SessionNotFoundError" } };
481
+ }
482
+ const client = await serviceOrThrow();
483
+ const info = und(await client.session.get?.({ sessionID: opts.path.id }));
484
+ return { data: info };
485
+ }
486
+ catch (e) {
487
+ if (isNotFoundError(e))
488
+ return { response: { status: 404 }, error: e };
489
+ return { error: e };
490
+ }
491
+ },
492
+ abort: async (opts) => {
493
+ try {
494
+ const client = await serviceOrThrow();
495
+ if (typeof client.session.interrupt !== "function")
496
+ throw new Error("registered service does not support session.interrupt");
497
+ const result = await client.session.interrupt({ sessionID: opts.path.id });
498
+ if (!result?.error)
499
+ return {};
500
+ }
501
+ catch {
502
+ // Fall through to the context-local interrupt below.
503
+ }
504
+ try {
505
+ await ctx().session.interrupt({ sessionID: opts.path.id });
506
+ }
507
+ catch {
508
+ // Best-effort, mirrors V1.
509
+ }
510
+ return {};
511
+ },
512
+ };
513
+ const config = {
514
+ get: async () => {
515
+ try {
516
+ const client = await serviceOrThrow();
517
+ const response = await client.config?.get({ location: { directory: ctx().location.directory } });
518
+ const documents = und(response);
519
+ const candidates = Array.isArray(documents) ? documents : documents?.type === "document" ? [documents] : [];
520
+ let info = {};
521
+ let found = false;
522
+ for (const entry of candidates) {
523
+ if (!entry || typeof entry !== "object" || entry.type !== "document")
524
+ continue;
525
+ if (!entry.info || typeof entry.info !== "object" || Array.isArray(entry.info))
526
+ continue;
527
+ info = { ...info, ...entry.info };
528
+ found = true;
529
+ }
530
+ if (!found)
531
+ return { data: {} };
532
+ const model = info.model;
533
+ let normalizedModel = model;
534
+ const modelRecord = model && typeof model === "object" ? model : null;
535
+ if (typeof modelRecord?.providerID === "string" && typeof modelRecord.model === "string") {
536
+ normalizedModel = `${modelRecord.providerID}/${modelRecord.model}`;
537
+ }
538
+ return { data: { ...info, ...(normalizedModel !== undefined ? { model: normalizedModel } : {}) } };
539
+ }
540
+ catch (e) {
541
+ return { error: e };
542
+ }
543
+ },
544
+ };
545
+ const provider = {
546
+ list: async () => {
547
+ try {
548
+ const res = await ctx().catalog.model.list();
549
+ return { data: adaptProviderCatalog(res) };
550
+ }
551
+ catch (e) {
552
+ return { error: e };
553
+ }
554
+ },
555
+ };
556
+ const mcp = {
557
+ status: async () => {
558
+ try {
559
+ const res = await ctx().mcp.list();
560
+ return { data: adaptMcpStatus(res) };
561
+ }
562
+ catch (e) {
563
+ return { error: e };
564
+ }
565
+ },
566
+ };
567
+ const _client = {
568
+ get: async (opts) => {
569
+ if (opts.url === "/experimental/session") {
570
+ const q = opts.query ?? {};
571
+ const cursor = typeof q.cursor === "number" || typeof q.cursor === "string" ? q.cursor : undefined;
572
+ return listGlobalSessions(typeof q.limit === "number" ? q.limit : 5000, cursor, typeof q.search === "string" ? q.search : undefined);
573
+ }
574
+ if (opts.url === "/provider") {
575
+ return provider.list();
576
+ }
577
+ return { error: { message: `unsupported shim route ${opts.url}` } };
578
+ },
579
+ };
580
+ return { session, config, provider, mcp, _client };
581
+ }