om-memory-system 3.0.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.
- package/LICENSE +21 -0
- package/README.md +587 -0
- package/dist/adapters/opencode/auto-capture-host.d.ts +3 -0
- package/dist/adapters/opencode/auto-capture-host.js +29 -0
- package/dist/adapters/opencode/auto-capture-summary.d.ts +4 -0
- package/dist/adapters/opencode/auto-capture-summary.js +147 -0
- package/dist/adapters/opencode/conversation.d.ts +2 -0
- package/dist/adapters/opencode/conversation.js +50 -0
- package/dist/adapters/opencode/user-prompt.d.ts +9 -0
- package/dist/adapters/opencode/user-prompt.js +26 -0
- package/dist/adapters/pi/capture.d.ts +41 -0
- package/dist/adapters/pi/capture.js +91 -0
- package/dist/adapters/pi/conversation.d.ts +38 -0
- package/dist/adapters/pi/conversation.js +118 -0
- package/dist/adapters/pi/extension.d.ts +18 -0
- package/dist/adapters/pi/extension.js +234 -0
- package/dist/adapters/pi/import-command.d.ts +29 -0
- package/dist/adapters/pi/import-command.js +232 -0
- package/dist/adapters/pi/profile.d.ts +39 -0
- package/dist/adapters/pi/profile.js +148 -0
- package/dist/adapters/pi/provider.d.ts +50 -0
- package/dist/adapters/pi/provider.js +77 -0
- package/dist/config.d.ts +290 -0
- package/dist/config.js +742 -0
- package/dist/core/capture-context.d.ts +4 -0
- package/dist/core/capture-context.js +132 -0
- package/dist/core/capture.d.ts +15 -0
- package/dist/core/capture.js +68 -0
- package/dist/core/extraction.d.ts +80 -0
- package/dist/core/extraction.js +140 -0
- package/dist/core/host.d.ts +52 -0
- package/dist/core/host.js +1 -0
- package/dist/core/memory-operations.d.ts +27 -0
- package/dist/core/memory-operations.js +247 -0
- package/dist/core/retrieval.d.ts +12 -0
- package/dist/core/retrieval.js +72 -0
- package/dist/importer/discovery.d.ts +31 -0
- package/dist/importer/discovery.js +88 -0
- package/dist/importer/importer.d.ts +87 -0
- package/dist/importer/importer.js +260 -0
- package/dist/importer/ledger.d.ts +44 -0
- package/dist/importer/ledger.js +137 -0
- package/dist/importer/session-loader.d.ts +19 -0
- package/dist/importer/session-loader.js +12 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +601 -0
- package/dist/plugin.d.ts +9 -0
- package/dist/plugin.js +11 -0
- package/dist/services/ai/ai-provider-factory.d.ts +7 -0
- package/dist/services/ai/ai-provider-factory.js +41 -0
- package/dist/services/ai/api-key-placeholder.d.ts +1 -0
- package/dist/services/ai/api-key-placeholder.js +14 -0
- package/dist/services/ai/internal-capture-sessions.d.ts +6 -0
- package/dist/services/ai/internal-capture-sessions.js +33 -0
- package/dist/services/ai/opencode-diagnostics.d.ts +7 -0
- package/dist/services/ai/opencode-diagnostics.js +36 -0
- package/dist/services/ai/opencode-host-config.d.ts +10 -0
- package/dist/services/ai/opencode-host-config.js +51 -0
- package/dist/services/ai/opencode-provider-loader.d.ts +3 -0
- package/dist/services/ai/opencode-provider-loader.js +4 -0
- package/dist/services/ai/opencode-provider.d.ts +92 -0
- package/dist/services/ai/opencode-provider.js +419 -0
- package/dist/services/ai/opencode-sdk-client.d.ts +6 -0
- package/dist/services/ai/opencode-sdk-client.js +38 -0
- package/dist/services/ai/profile-llm-client.d.ts +2 -0
- package/dist/services/ai/profile-llm-client.js +27 -0
- package/dist/services/ai/provider-config.d.ts +17 -0
- package/dist/services/ai/provider-config.js +30 -0
- package/dist/services/ai/providers/anthropic-messages.d.ts +34 -0
- package/dist/services/ai/providers/anthropic-messages.js +220 -0
- package/dist/services/ai/providers/base-provider.d.ts +24 -0
- package/dist/services/ai/providers/base-provider.js +24 -0
- package/dist/services/ai/providers/google-gemini.d.ts +15 -0
- package/dist/services/ai/providers/google-gemini.js +228 -0
- package/dist/services/ai/providers/minimax.d.ts +35 -0
- package/dist/services/ai/providers/minimax.js +55 -0
- package/dist/services/ai/providers/openai-chat-completion.d.ts +22 -0
- package/dist/services/ai/providers/openai-chat-completion.js +387 -0
- package/dist/services/ai/providers/openai-responses.d.ts +13 -0
- package/dist/services/ai/providers/openai-responses.js +182 -0
- package/dist/services/ai/providers/orcarouter.d.ts +51 -0
- package/dist/services/ai/providers/orcarouter.js +66 -0
- package/dist/services/ai/session/ai-session-manager.d.ts +24 -0
- package/dist/services/ai/session/ai-session-manager.js +211 -0
- package/dist/services/ai/session/session-types.d.ts +42 -0
- package/dist/services/ai/session/session-types.js +1 -0
- package/dist/services/ai/tools/tool-schema.d.ts +40 -0
- package/dist/services/ai/tools/tool-schema.js +24 -0
- package/dist/services/ai/validators/user-profile-validator.d.ts +12 -0
- package/dist/services/ai/validators/user-profile-validator.js +114 -0
- package/dist/services/api-handlers.d.ts +186 -0
- package/dist/services/api-handlers.js +1284 -0
- package/dist/services/auth-token.d.ts +14 -0
- package/dist/services/auth-token.js +59 -0
- package/dist/services/auto-capture.d.ts +3 -0
- package/dist/services/auto-capture.js +127 -0
- package/dist/services/cleanup-service.d.ts +22 -0
- package/dist/services/cleanup-service.js +109 -0
- package/dist/services/client.d.ts +134 -0
- package/dist/services/client.js +327 -0
- package/dist/services/context.d.ts +10 -0
- package/dist/services/context.js +27 -0
- package/dist/services/cors.d.ts +12 -0
- package/dist/services/cors.js +52 -0
- package/dist/services/deduplication-service.d.ts +28 -0
- package/dist/services/deduplication-service.js +111 -0
- package/dist/services/embedding.d.ts +36 -0
- package/dist/services/embedding.js +200 -0
- package/dist/services/jsonc.d.ts +6 -0
- package/dist/services/jsonc.js +76 -0
- package/dist/services/language-detector.d.ts +2 -0
- package/dist/services/language-detector.js +33 -0
- package/dist/services/legacy-migration.d.ts +77 -0
- package/dist/services/legacy-migration.js +343 -0
- package/dist/services/logger.d.ts +1 -0
- package/dist/services/logger.js +119 -0
- package/dist/services/memory-portability-service.d.ts +37 -0
- package/dist/services/memory-portability-service.js +340 -0
- package/dist/services/memory-scope.d.ts +16 -0
- package/dist/services/memory-scope.js +43 -0
- package/dist/services/migration-service.d.ts +42 -0
- package/dist/services/migration-service.js +307 -0
- package/dist/services/onnxruntime-resolve.d.ts +24 -0
- package/dist/services/onnxruntime-resolve.js +174 -0
- package/dist/services/privacy.d.ts +26 -0
- package/dist/services/privacy.js +69 -0
- package/dist/services/request-timeouts.d.ts +9 -0
- package/dist/services/request-timeouts.js +11 -0
- package/dist/services/runtime-require.d.ts +17 -0
- package/dist/services/runtime-require.js +75 -0
- package/dist/services/secret-resolver.d.ts +1 -0
- package/dist/services/secret-resolver.js +65 -0
- package/dist/services/shard-inventory-service.d.ts +69 -0
- package/dist/services/shard-inventory-service.js +315 -0
- package/dist/services/shard-path-migration-service.d.ts +31 -0
- package/dist/services/shard-path-migration-service.js +367 -0
- package/dist/services/tag-prefix-migration.d.ts +60 -0
- package/dist/services/tag-prefix-migration.js +439 -0
- package/dist/services/tag-prefix-scan.d.ts +26 -0
- package/dist/services/tag-prefix-scan.js +91 -0
- package/dist/services/tags.d.ts +29 -0
- package/dist/services/tags.js +251 -0
- package/dist/services/turso/connection-manager.d.ts +15 -0
- package/dist/services/turso/connection-manager.js +163 -0
- package/dist/services/turso/cross-process-write-lock.d.ts +1 -0
- package/dist/services/turso/cross-process-write-lock.js +89 -0
- package/dist/services/turso/legacy-migrator.d.ts +1 -0
- package/dist/services/turso/legacy-migrator.js +540 -0
- package/dist/services/turso/lifecycle.d.ts +1 -0
- package/dist/services/turso/lifecycle.js +14 -0
- package/dist/services/turso/operation-lock.d.ts +2 -0
- package/dist/services/turso/operation-lock.js +61 -0
- package/dist/services/turso/ready.d.ts +2 -0
- package/dist/services/turso/ready.js +36 -0
- package/dist/services/turso/shard-manager.d.ts +38 -0
- package/dist/services/turso/shard-manager.js +496 -0
- package/dist/services/turso/sqlite-handle-release.d.ts +16 -0
- package/dist/services/turso/sqlite-handle-release.js +56 -0
- package/dist/services/turso/turso-db.d.ts +18 -0
- package/dist/services/turso/turso-db.js +53 -0
- package/dist/services/turso/types.d.ts +42 -0
- package/dist/services/turso/types.js +1 -0
- package/dist/services/turso/vector-search.d.ts +41 -0
- package/dist/services/turso/vector-search.js +384 -0
- package/dist/services/turso/vector-utils.d.ts +7 -0
- package/dist/services/turso/vector-utils.js +67 -0
- package/dist/services/user-memory-learning.d.ts +97 -0
- package/dist/services/user-memory-learning.js +673 -0
- package/dist/services/user-profile/ai-cleanup.d.ts +58 -0
- package/dist/services/user-profile/ai-cleanup.js +486 -0
- package/dist/services/user-profile/profile-context.d.ts +1 -0
- package/dist/services/user-profile/profile-context.js +105 -0
- package/dist/services/user-profile/profile-utils.d.ts +2 -0
- package/dist/services/user-profile/profile-utils.js +45 -0
- package/dist/services/user-profile/types.d.ts +92 -0
- package/dist/services/user-profile/types.js +1 -0
- package/dist/services/user-profile/user-profile-manager.d.ts +65 -0
- package/dist/services/user-profile/user-profile-manager.js +1660 -0
- package/dist/services/user-prompt/user-prompt-manager.d.ts +53 -0
- package/dist/services/user-prompt/user-prompt-manager.js +303 -0
- package/dist/services/web-api-auth.d.ts +3 -0
- package/dist/services/web-api-auth.js +25 -0
- package/dist/services/web-auth.d.ts +46 -0
- package/dist/services/web-auth.js +105 -0
- package/dist/services/web-server.d.ts +57 -0
- package/dist/services/web-server.js +659 -0
- package/dist/shared/api/index.d.ts +4 -0
- package/dist/shared/api/index.js +2 -0
- package/dist/shared/api/portability-schemas.d.ts +56 -0
- package/dist/shared/api/portability-schemas.js +39 -0
- package/dist/shared/api/schemas.d.ts +295 -0
- package/dist/shared/api/schemas.js +134 -0
- package/dist/shared/api/types.d.ts +28 -0
- package/dist/shared/api/types.js +1 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/context-limit.d.ts +10 -0
- package/dist/utils/context-limit.js +43 -0
- package/dist/utils/math.d.ts +7 -0
- package/dist/utils/math.js +48 -0
- package/dist/utils/profile.d.ts +2 -0
- package/dist/utils/profile.js +15 -0
- package/dist/v2/adapter.d.ts +5 -0
- package/dist/v2/adapter.js +218 -0
- package/dist/v2/legacy-client.d.ts +15 -0
- package/dist/v2/legacy-client.js +257 -0
- package/dist/v2/memory-bridge.d.ts +18 -0
- package/dist/v2/memory-bridge.js +29 -0
- package/dist/v2/plugin.d.ts +3 -0
- package/dist/v2/plugin.js +24 -0
- package/dist/web/assets/index-DPjEpD1h.css +2 -0
- package/dist/web/assets/index-do26c4JJ.js +122 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +14 -0
- package/dist/web/omms-icon.png +0 -0
- package/package.json +138 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const PLACEHOLDER_API_KEYS = new Set([
|
|
2
|
+
"sk-...",
|
|
3
|
+
"sk-ant-...",
|
|
4
|
+
"gsk_...",
|
|
5
|
+
"your-api-key",
|
|
6
|
+
"your api key",
|
|
7
|
+
"replace-with-your-api-key",
|
|
8
|
+
]);
|
|
9
|
+
export function isPlaceholderApiKey(value) {
|
|
10
|
+
if (!value) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return PLACEHOLDER_API_KEYS.has(value.trim().toLowerCase());
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Title used for transient structured-output sessions (capture / profile learning). */
|
|
2
|
+
export declare const INTERNAL_CAPTURE_SESSION_TITLE = "omms capture";
|
|
3
|
+
export declare function isInternalCaptureSessionTitle(title: string | undefined | null): boolean;
|
|
4
|
+
export declare function trackInternalCaptureSession(sessionID: string): void;
|
|
5
|
+
export declare function untrackInternalCaptureSession(sessionID: string): void;
|
|
6
|
+
export declare function isTrackedInternalCaptureSession(sessionID: string): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Title used for transient structured-output sessions (capture / profile learning). */
|
|
2
|
+
export const INTERNAL_CAPTURE_SESSION_TITLE = "omms capture";
|
|
3
|
+
/** Title used by opencode-mem builds; sessions left by older versions stay internal. */
|
|
4
|
+
const LEGACY_INTERNAL_CAPTURE_SESSION_TITLE = "opencode-mem capture";
|
|
5
|
+
/** Grace period so session.idle can still match after best-effort delete. */
|
|
6
|
+
const UNTRACK_GRACE_MS = 60_000;
|
|
7
|
+
const trackedSessionIDs = new Set();
|
|
8
|
+
const untrackTimers = new Map();
|
|
9
|
+
export function isInternalCaptureSessionTitle(title) {
|
|
10
|
+
return (title === INTERNAL_CAPTURE_SESSION_TITLE || title === LEGACY_INTERNAL_CAPTURE_SESSION_TITLE);
|
|
11
|
+
}
|
|
12
|
+
export function trackInternalCaptureSession(sessionID) {
|
|
13
|
+
const pending = untrackTimers.get(sessionID);
|
|
14
|
+
if (pending) {
|
|
15
|
+
clearTimeout(pending);
|
|
16
|
+
untrackTimers.delete(sessionID);
|
|
17
|
+
}
|
|
18
|
+
trackedSessionIDs.add(sessionID);
|
|
19
|
+
}
|
|
20
|
+
export function untrackInternalCaptureSession(sessionID) {
|
|
21
|
+
const pending = untrackTimers.get(sessionID);
|
|
22
|
+
if (pending) {
|
|
23
|
+
clearTimeout(pending);
|
|
24
|
+
}
|
|
25
|
+
const timer = setTimeout(() => {
|
|
26
|
+
trackedSessionIDs.delete(sessionID);
|
|
27
|
+
untrackTimers.delete(sessionID);
|
|
28
|
+
}, UNTRACK_GRACE_MS);
|
|
29
|
+
untrackTimers.set(sessionID, timer);
|
|
30
|
+
}
|
|
31
|
+
export function isTrackedInternalCaptureSession(sessionID) {
|
|
32
|
+
return trackedSessionIDs.has(sessionID);
|
|
33
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface FetchEndpoint {
|
|
2
|
+
readonly label: string;
|
|
3
|
+
readonly url: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function diagnosticUrl(url: string): string;
|
|
6
|
+
export declare function responseStatus(res: Response): string;
|
|
7
|
+
export declare function readJson<T>(res: Response, endpoint: FetchEndpoint): Promise<T>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function diagnosticUrl(url) {
|
|
2
|
+
try {
|
|
3
|
+
const parsed = new URL(url);
|
|
4
|
+
parsed.search = "";
|
|
5
|
+
return parsed.toString();
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return url.split("?")[0] ?? url;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function responseStatus(res) {
|
|
12
|
+
const statusTextByCode = {
|
|
13
|
+
500: "Internal Server Error",
|
|
14
|
+
502: "Bad Gateway",
|
|
15
|
+
};
|
|
16
|
+
return `${res.status} ${res.statusText || statusTextByCode[res.status] || "Unknown Status"}`;
|
|
17
|
+
}
|
|
18
|
+
function redactedBody(text) {
|
|
19
|
+
return text ? "<redacted response body>" : "<empty body>";
|
|
20
|
+
}
|
|
21
|
+
export async function readJson(res, endpoint) {
|
|
22
|
+
const text = await res.text();
|
|
23
|
+
const url = diagnosticUrl(endpoint.url);
|
|
24
|
+
if (!res.ok) {
|
|
25
|
+
throw new Error(`omms: opencode ${endpoint.label} failed at ${url} (${responseStatus(res)}): ${redactedBody(text)}`);
|
|
26
|
+
}
|
|
27
|
+
if (!text) {
|
|
28
|
+
throw new Error(`omms: opencode ${endpoint.label} at ${url} returned an empty response body`);
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(text);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error(`omms: opencode ${endpoint.label} at ${url} returned non-JSON body: ${redactedBody(text)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type HostClientConfig = {
|
|
2
|
+
readonly baseUrl: string | undefined;
|
|
3
|
+
readonly fetch: typeof fetch | undefined;
|
|
4
|
+
readonly headers?: RequestInit["headers"];
|
|
5
|
+
readonly clientKeys: readonly string[];
|
|
6
|
+
readonly sdkConfigCount: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function getHostClientConfig(ctx: {
|
|
9
|
+
readonly client: unknown;
|
|
10
|
+
}): HostClientConfig;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function getHostClientConfig(ctx) {
|
|
2
|
+
const client = toRecord(ctx.client);
|
|
3
|
+
if (!client) {
|
|
4
|
+
return { baseUrl: undefined, fetch: undefined, clientKeys: [], sdkConfigCount: 0 };
|
|
5
|
+
}
|
|
6
|
+
const configs = sdkConfigs(client);
|
|
7
|
+
const baseUrl = configs.find((config) => typeof config["baseUrl"] === "string")?.["baseUrl"];
|
|
8
|
+
const customFetch = configs.find((config) => isFetch(config["fetch"]))?.["fetch"];
|
|
9
|
+
const headers = configs.find((config) => isHeadersInit(config["headers"]))?.["headers"];
|
|
10
|
+
return {
|
|
11
|
+
baseUrl: typeof baseUrl === "string" ? baseUrl : undefined,
|
|
12
|
+
fetch: isFetch(customFetch) ? customFetch : undefined,
|
|
13
|
+
...(isHeadersInit(headers) ? { headers } : {}),
|
|
14
|
+
clientKeys: Object.keys(client),
|
|
15
|
+
sdkConfigCount: configs.length,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function sdkConfigs(client) {
|
|
19
|
+
const configs = [];
|
|
20
|
+
const nestedClients = Object.values(client).flatMap((value) => {
|
|
21
|
+
const record = toRecord(value);
|
|
22
|
+
return record ? [record] : [];
|
|
23
|
+
});
|
|
24
|
+
const candidates = [client, ...nestedClients];
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
const sdkClient = toRecord(candidate["_client"]);
|
|
27
|
+
const getConfig = sdkClient?.["getConfig"];
|
|
28
|
+
if (!isConfigGetter(getConfig))
|
|
29
|
+
continue;
|
|
30
|
+
const config = toRecord(getConfig.call(sdkClient));
|
|
31
|
+
if (config)
|
|
32
|
+
configs.push(config);
|
|
33
|
+
}
|
|
34
|
+
return configs;
|
|
35
|
+
}
|
|
36
|
+
function toRecord(value) {
|
|
37
|
+
if (typeof value !== "object" || value === null)
|
|
38
|
+
return undefined;
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
function isConfigGetter(value) {
|
|
42
|
+
return typeof value === "function";
|
|
43
|
+
}
|
|
44
|
+
function isFetch(value) {
|
|
45
|
+
return typeof value === "function";
|
|
46
|
+
}
|
|
47
|
+
function isHeadersInit(value) {
|
|
48
|
+
return (value instanceof Headers ||
|
|
49
|
+
Array.isArray(value) ||
|
|
50
|
+
(typeof value === "object" && value !== null));
|
|
51
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured output via the opencode HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the older auth.json/OAuth-juggling flow. Instead of forging
|
|
5
|
+
* requests to provider HTTP endpoints ourselves, we delegate to the
|
|
6
|
+
* running opencode server: it already owns the user's auth (any provider,
|
|
7
|
+
* including github-copilot personal/business), token refresh, and provider
|
|
8
|
+
* routing.
|
|
9
|
+
*
|
|
10
|
+
* Per call we create a transient session, prompt it with a JSON schema,
|
|
11
|
+
* then delete the session so it does not pollute the user's TUI session
|
|
12
|
+
* list.
|
|
13
|
+
*
|
|
14
|
+
* Internal capture sessions are least-privilege (issue #189): ordinary
|
|
15
|
+
* agent tools are denied, only StructuredOutput is allowed, a dedicated
|
|
16
|
+
* agent caps steps, and a hard timeout fails closed.
|
|
17
|
+
*
|
|
18
|
+
* The primary transport is the authenticated v2 SDK client initialized from
|
|
19
|
+
* the plugin host's client configuration. A raw fetch fallback remains for
|
|
20
|
+
* older SDK builds that do not expose the v2 session methods.
|
|
21
|
+
*/
|
|
22
|
+
import type { z } from "zod";
|
|
23
|
+
import type { OpencodeClient } from "@opencode-ai/sdk/v2/client";
|
|
24
|
+
import { type HostTransport } from "./opencode-sdk-client.js";
|
|
25
|
+
/** Dedicated agent registered via the plugin config hook (step-capped). */
|
|
26
|
+
export declare const STRUCTURED_OUTPUT_AGENT = "omms-structured";
|
|
27
|
+
/** Hard ceiling for a single internal structured-output prompt. */
|
|
28
|
+
export declare const STRUCTURED_OUTPUT_TIMEOUT_MS = 90000;
|
|
29
|
+
/** Test helper: override the structured-output prompt timeout. Pass undefined to reset. */
|
|
30
|
+
export declare function setStructuredOutputTimeoutMsForTests(ms: number | undefined): void;
|
|
31
|
+
export declare const STRUCTURED_OUTPUT_PERMISSIONS: ({
|
|
32
|
+
permission: string;
|
|
33
|
+
pattern: string;
|
|
34
|
+
action: "deny";
|
|
35
|
+
} | {
|
|
36
|
+
permission: string;
|
|
37
|
+
pattern: string;
|
|
38
|
+
action: "allow";
|
|
39
|
+
})[];
|
|
40
|
+
export declare const STRUCTURED_OUTPUT_TOOLS: Record<string, boolean>;
|
|
41
|
+
export declare const STRUCTURED_OUTPUT_METADATA: {
|
|
42
|
+
omms: {
|
|
43
|
+
internal: boolean;
|
|
44
|
+
purpose: string;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
export declare function setHostFetch(customFetch: typeof fetch): void;
|
|
48
|
+
export declare function resetHostFetch(): void;
|
|
49
|
+
export declare function setConnectedProviders(providers: string[]): void;
|
|
50
|
+
export declare function isProviderConnected(providerName: string): boolean;
|
|
51
|
+
export declare function setV2Client(client: OpencodeClient): void;
|
|
52
|
+
export declare function getV2Client(): OpencodeClient | undefined;
|
|
53
|
+
export declare function createV2Client(serverUrl: URL | string, transport?: HostTransport): OpencodeClient;
|
|
54
|
+
/** True while an internal structured-output session is live (create → delete). */
|
|
55
|
+
export declare function isInternalStructuredSession(sessionID: string): boolean;
|
|
56
|
+
/** Test helper: clear tracked internal session IDs. */
|
|
57
|
+
export declare function resetInternalStructuredSessions(): void;
|
|
58
|
+
export interface StructuredOutputOptions<T> {
|
|
59
|
+
client: OpencodeClient;
|
|
60
|
+
providerID: string;
|
|
61
|
+
modelID: string;
|
|
62
|
+
systemPrompt: string;
|
|
63
|
+
userPrompt: string;
|
|
64
|
+
schema: z.ZodType<T>;
|
|
65
|
+
directory?: string;
|
|
66
|
+
retryCount?: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve `opencodeModel: "inherit"` to a concrete provider/model.
|
|
70
|
+
*
|
|
71
|
+
* Prefer an explicit prompt-recorded model (auto-capture path). Otherwise fall
|
|
72
|
+
* back to OpenCode's recent model list so profile-learning / conflict / dedup
|
|
73
|
+
* paths don't send the literal model id "inherit" (ProviderModelNotFoundError).
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveOpencodeModelRef(opts: {
|
|
76
|
+
providerID: string;
|
|
77
|
+
modelID: string;
|
|
78
|
+
prompt?: {
|
|
79
|
+
providerId?: string | null;
|
|
80
|
+
modelId?: string | null;
|
|
81
|
+
};
|
|
82
|
+
}): {
|
|
83
|
+
providerID: string;
|
|
84
|
+
modelID: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Generate one structured-output completion via opencode's HTTP API.
|
|
88
|
+
* Throws on: session.create failure, prompt failure, AssistantMessage.error
|
|
89
|
+
* (StructuredOutputError / ApiError / ...), missing `info.structured`,
|
|
90
|
+
* timeout, or final Zod validation failure.
|
|
91
|
+
*/
|
|
92
|
+
export declare function generateStructuredOutput<T>(opts: StructuredOutputOptions<T>): Promise<T>;
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured output via the opencode HTTP server.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the older auth.json/OAuth-juggling flow. Instead of forging
|
|
5
|
+
* requests to provider HTTP endpoints ourselves, we delegate to the
|
|
6
|
+
* running opencode server: it already owns the user's auth (any provider,
|
|
7
|
+
* including github-copilot personal/business), token refresh, and provider
|
|
8
|
+
* routing.
|
|
9
|
+
*
|
|
10
|
+
* Per call we create a transient session, prompt it with a JSON schema,
|
|
11
|
+
* then delete the session so it does not pollute the user's TUI session
|
|
12
|
+
* list.
|
|
13
|
+
*
|
|
14
|
+
* Internal capture sessions are least-privilege (issue #189): ordinary
|
|
15
|
+
* agent tools are denied, only StructuredOutput is allowed, a dedicated
|
|
16
|
+
* agent caps steps, and a hard timeout fails closed.
|
|
17
|
+
*
|
|
18
|
+
* The primary transport is the authenticated v2 SDK client initialized from
|
|
19
|
+
* the plugin host's client configuration. A raw fetch fallback remains for
|
|
20
|
+
* older SDK builds that do not expose the v2 session methods.
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import { diagnosticUrl, readJson, responseStatus, } from "./opencode-diagnostics.js";
|
|
26
|
+
import { INTERNAL_CAPTURE_SESSION_TITLE, trackInternalCaptureSession, untrackInternalCaptureSession, } from "./internal-capture-sessions.js";
|
|
27
|
+
import { createLazyV2Client } from "./opencode-sdk-client.js";
|
|
28
|
+
/** Dedicated agent registered via the plugin config hook (step-capped). */
|
|
29
|
+
export const STRUCTURED_OUTPUT_AGENT = "omms-structured";
|
|
30
|
+
/** Hard ceiling for a single internal structured-output prompt. */
|
|
31
|
+
export const STRUCTURED_OUTPUT_TIMEOUT_MS = 90_000;
|
|
32
|
+
let _structuredOutputTimeoutMs = STRUCTURED_OUTPUT_TIMEOUT_MS;
|
|
33
|
+
/** Test helper: override the structured-output prompt timeout. Pass undefined to reset. */
|
|
34
|
+
export function setStructuredOutputTimeoutMsForTests(ms) {
|
|
35
|
+
_structuredOutputTimeoutMs = ms ?? STRUCTURED_OUTPUT_TIMEOUT_MS;
|
|
36
|
+
}
|
|
37
|
+
export const STRUCTURED_OUTPUT_PERMISSIONS = [
|
|
38
|
+
{ permission: "*", pattern: "*", action: "deny" },
|
|
39
|
+
{ permission: "StructuredOutput", pattern: "*", action: "allow" },
|
|
40
|
+
];
|
|
41
|
+
export const STRUCTURED_OUTPUT_TOOLS = {
|
|
42
|
+
"*": false,
|
|
43
|
+
StructuredOutput: true,
|
|
44
|
+
};
|
|
45
|
+
export const STRUCTURED_OUTPUT_METADATA = {
|
|
46
|
+
omms: {
|
|
47
|
+
internal: true,
|
|
48
|
+
purpose: "structured-output",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
const _internalSessions = new Set();
|
|
52
|
+
let _connectedProviders = new Set();
|
|
53
|
+
let _v2Client;
|
|
54
|
+
let _v2BaseUrl;
|
|
55
|
+
let _hostFetch;
|
|
56
|
+
let _useSdkTransport = false;
|
|
57
|
+
export function setHostFetch(customFetch) {
|
|
58
|
+
_hostFetch = customFetch;
|
|
59
|
+
}
|
|
60
|
+
export function resetHostFetch() {
|
|
61
|
+
_hostFetch = undefined;
|
|
62
|
+
}
|
|
63
|
+
export function setConnectedProviders(providers) {
|
|
64
|
+
_connectedProviders = new Set(providers);
|
|
65
|
+
}
|
|
66
|
+
export function isProviderConnected(providerName) {
|
|
67
|
+
return _connectedProviders.has(providerName);
|
|
68
|
+
}
|
|
69
|
+
export function setV2Client(client) {
|
|
70
|
+
_v2Client = client;
|
|
71
|
+
}
|
|
72
|
+
export function getV2Client() {
|
|
73
|
+
return _v2Client;
|
|
74
|
+
}
|
|
75
|
+
export function createV2Client(serverUrl, transport) {
|
|
76
|
+
const baseUrl = typeof serverUrl === "string" ? serverUrl : serverUrl.toString();
|
|
77
|
+
const activeTransport = transport ?? (_hostFetch ? { fetch: _hostFetch } : undefined);
|
|
78
|
+
_v2BaseUrl = baseUrl;
|
|
79
|
+
_useSdkTransport = Boolean(activeTransport?.fetch || activeTransport?.headers);
|
|
80
|
+
return createLazyV2Client(baseUrl, activeTransport);
|
|
81
|
+
}
|
|
82
|
+
/** True while an internal structured-output session is live (create → delete). */
|
|
83
|
+
export function isInternalStructuredSession(sessionID) {
|
|
84
|
+
return _internalSessions.has(sessionID);
|
|
85
|
+
}
|
|
86
|
+
/** Test helper: clear tracked internal session IDs. */
|
|
87
|
+
export function resetInternalStructuredSessions() {
|
|
88
|
+
_internalSessions.clear();
|
|
89
|
+
}
|
|
90
|
+
function markInternalSession(sessionID) {
|
|
91
|
+
_internalSessions.add(sessionID);
|
|
92
|
+
}
|
|
93
|
+
function unmarkInternalSession(sessionID) {
|
|
94
|
+
_internalSessions.delete(sessionID);
|
|
95
|
+
}
|
|
96
|
+
function sessionCreateBody() {
|
|
97
|
+
return {
|
|
98
|
+
title: INTERNAL_CAPTURE_SESSION_TITLE,
|
|
99
|
+
permission: STRUCTURED_OUTPUT_PERMISSIONS,
|
|
100
|
+
metadata: STRUCTURED_OUTPUT_METADATA,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function sessionPromptFields(args) {
|
|
104
|
+
return {
|
|
105
|
+
model: { providerID: args.providerID, modelID: args.modelID },
|
|
106
|
+
agent: STRUCTURED_OUTPUT_AGENT,
|
|
107
|
+
system: args.systemPrompt,
|
|
108
|
+
parts: [{ type: "text", text: args.userPrompt }],
|
|
109
|
+
tools: STRUCTURED_OUTPUT_TOOLS,
|
|
110
|
+
// `noReply` suppresses assistant generation in current OpenCode builds,
|
|
111
|
+
// which also suppresses `info.structured_output`; structured capture needs
|
|
112
|
+
// the assistant run even though the temporary session is deleted afterward.
|
|
113
|
+
format: {
|
|
114
|
+
type: "json_schema",
|
|
115
|
+
schema: args.jsonSchema,
|
|
116
|
+
...(args.retryCount !== undefined ? { retryCount: args.retryCount } : {}),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Resolve `opencodeModel: "inherit"` to a concrete provider/model.
|
|
122
|
+
*
|
|
123
|
+
* Prefer an explicit prompt-recorded model (auto-capture path). Otherwise fall
|
|
124
|
+
* back to OpenCode's recent model list so profile-learning / conflict / dedup
|
|
125
|
+
* paths don't send the literal model id "inherit" (ProviderModelNotFoundError).
|
|
126
|
+
*/
|
|
127
|
+
export function resolveOpencodeModelRef(opts) {
|
|
128
|
+
if (opts.modelID !== "inherit") {
|
|
129
|
+
return { providerID: opts.providerID, modelID: opts.modelID };
|
|
130
|
+
}
|
|
131
|
+
if (opts.prompt?.providerId && opts.prompt?.modelId) {
|
|
132
|
+
return { providerID: opts.prompt.providerId, modelID: opts.prompt.modelId };
|
|
133
|
+
}
|
|
134
|
+
const recent = readRecentOpencodeModel(opts.providerID);
|
|
135
|
+
if (recent)
|
|
136
|
+
return recent;
|
|
137
|
+
throw new Error("omms: opencodeModel is 'inherit' but no session model was recorded and no recent OpenCode model is available");
|
|
138
|
+
}
|
|
139
|
+
function readRecentOpencodeModel(preferredProvider) {
|
|
140
|
+
try {
|
|
141
|
+
// OpenCode state path mirrors `opencode debug paths` → state.
|
|
142
|
+
const stateDir = process.env.XDG_STATE_HOME || join(homedir(), ".local", "state");
|
|
143
|
+
const modelPath = join(stateDir, "opencode", "model.json");
|
|
144
|
+
if (!existsSync(modelPath))
|
|
145
|
+
return undefined;
|
|
146
|
+
const raw = JSON.parse(readFileSync(modelPath, "utf8"));
|
|
147
|
+
const recent = Array.isArray(raw.recent) ? raw.recent : [];
|
|
148
|
+
const match = (preferredProvider
|
|
149
|
+
? recent.find((r) => r.providerID === preferredProvider && r.modelID)
|
|
150
|
+
: undefined) ?? recent.find((r) => r.providerID && r.modelID);
|
|
151
|
+
if (!match?.providerID || !match.modelID)
|
|
152
|
+
return undefined;
|
|
153
|
+
return { providerID: match.providerID, modelID: match.modelID };
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Generate one structured-output completion via opencode's HTTP API.
|
|
161
|
+
* Throws on: session.create failure, prompt failure, AssistantMessage.error
|
|
162
|
+
* (StructuredOutputError / ApiError / ...), missing `info.structured`,
|
|
163
|
+
* timeout, or final Zod validation failure.
|
|
164
|
+
*/
|
|
165
|
+
export async function generateStructuredOutput(opts) {
|
|
166
|
+
const resolved = resolveOpencodeModelRef({
|
|
167
|
+
providerID: opts.providerID,
|
|
168
|
+
modelID: opts.modelID,
|
|
169
|
+
});
|
|
170
|
+
const { client, systemPrompt, userPrompt, schema, directory, retryCount } = opts;
|
|
171
|
+
const { providerID, modelID } = resolved;
|
|
172
|
+
const jsonSchema = schema.toJSONSchema?.() ?? (await import("zod")).z.toJSONSchema(schema);
|
|
173
|
+
if (_useSdkTransport && hasV2SessionClient(client)) {
|
|
174
|
+
return generateViaSdkClient(client, {
|
|
175
|
+
providerID,
|
|
176
|
+
modelID,
|
|
177
|
+
systemPrompt,
|
|
178
|
+
userPrompt,
|
|
179
|
+
directory,
|
|
180
|
+
retryCount,
|
|
181
|
+
jsonSchema,
|
|
182
|
+
schema,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
const baseUrl = _v2BaseUrl;
|
|
186
|
+
if (!baseUrl) {
|
|
187
|
+
throw new Error("omms: v2 server base URL not initialized; call createV2Client(serverUrl) first");
|
|
188
|
+
}
|
|
189
|
+
const base = stripTrailingSlash(baseUrl);
|
|
190
|
+
const sessionID = await createSession(base, directory);
|
|
191
|
+
markInternalSession(sessionID);
|
|
192
|
+
try {
|
|
193
|
+
const info = await withStructuredOutputTimeout(() => promptSession(base, {
|
|
194
|
+
sessionID,
|
|
195
|
+
directory,
|
|
196
|
+
providerID,
|
|
197
|
+
modelID,
|
|
198
|
+
systemPrompt,
|
|
199
|
+
userPrompt,
|
|
200
|
+
jsonSchema,
|
|
201
|
+
retryCount,
|
|
202
|
+
}), () => abortSession(base, sessionID, directory));
|
|
203
|
+
if (info.error) {
|
|
204
|
+
throw new Error(`omms: opencode reported ${info.error.name}: ${formatAssistantError(info.error)}`);
|
|
205
|
+
}
|
|
206
|
+
const structuredOutput = info.structured_output ?? info.structured;
|
|
207
|
+
if (structuredOutput === undefined || structuredOutput === null) {
|
|
208
|
+
throw new Error("omms: opencode returned no structured output (info.structured_output/info.structured were empty)");
|
|
209
|
+
}
|
|
210
|
+
return schema.parse(structuredOutput);
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
unmarkInternalSession(sessionID);
|
|
214
|
+
// Best-effort: leaving a transient session behind is cosmetic, not
|
|
215
|
+
// worth failing a successful capture if cleanup itself errors.
|
|
216
|
+
try {
|
|
217
|
+
await deleteSession(base, sessionID, directory);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
// intentionally swallowed
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
untrackInternalCaptureSession(sessionID);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function hasV2SessionClient(client) {
|
|
228
|
+
const session = client.session;
|
|
229
|
+
if (typeof session !== "object" || session === null)
|
|
230
|
+
return false;
|
|
231
|
+
const candidate = session;
|
|
232
|
+
return (typeof candidate.create === "function" &&
|
|
233
|
+
typeof candidate.prompt === "function" &&
|
|
234
|
+
typeof candidate.delete === "function");
|
|
235
|
+
}
|
|
236
|
+
async function generateViaSdkClient(client, args) {
|
|
237
|
+
const createdResponse = await client.session.create({
|
|
238
|
+
...sessionCreateBody(),
|
|
239
|
+
...(args.directory ? { directory: args.directory } : {}),
|
|
240
|
+
});
|
|
241
|
+
const created = readSdkData(createdResponse, "POST /session");
|
|
242
|
+
if (!created.id) {
|
|
243
|
+
throw new Error("omms: session.create returned no session id; cannot generate structured output");
|
|
244
|
+
}
|
|
245
|
+
const sessionID = created.id;
|
|
246
|
+
trackInternalCaptureSession(sessionID);
|
|
247
|
+
markInternalSession(sessionID);
|
|
248
|
+
try {
|
|
249
|
+
const promptResponse = await withStructuredOutputTimeout(() => client.session.prompt({
|
|
250
|
+
sessionID,
|
|
251
|
+
...(args.directory ? { directory: args.directory } : {}),
|
|
252
|
+
...sessionPromptFields(args),
|
|
253
|
+
}), () => client.session.abort?.({
|
|
254
|
+
sessionID,
|
|
255
|
+
...(args.directory ? { directory: args.directory } : {}),
|
|
256
|
+
}));
|
|
257
|
+
const data = readSdkData(promptResponse, "POST /session/{id}/message");
|
|
258
|
+
if (!data.info) {
|
|
259
|
+
throw new Error("omms: prompt response missing `info`");
|
|
260
|
+
}
|
|
261
|
+
if (data.info.error) {
|
|
262
|
+
throw new Error(`omms: opencode reported ${data.info.error.name}: ${formatAssistantError(data.info.error)}`);
|
|
263
|
+
}
|
|
264
|
+
const structuredOutput = data.info.structured_output ?? data.info.structured;
|
|
265
|
+
if (structuredOutput === undefined || structuredOutput === null) {
|
|
266
|
+
throw new Error("omms: opencode returned no structured output (info.structured_output/info.structured were empty)");
|
|
267
|
+
}
|
|
268
|
+
return args.schema.parse(structuredOutput);
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
unmarkInternalSession(sessionID);
|
|
272
|
+
try {
|
|
273
|
+
await client.session.delete({
|
|
274
|
+
sessionID,
|
|
275
|
+
...(args.directory ? { directory: args.directory } : {}),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
// Best-effort cleanup for the transient capture session.
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
untrackInternalCaptureSession(sessionID);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function withStructuredOutputTimeout(run, onTimeout) {
|
|
287
|
+
let timer;
|
|
288
|
+
const timeoutMs = _structuredOutputTimeoutMs;
|
|
289
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
290
|
+
timer = setTimeout(() => {
|
|
291
|
+
reject(new Error(`omms: structured-output timed out after ${timeoutMs}ms`));
|
|
292
|
+
}, timeoutMs);
|
|
293
|
+
});
|
|
294
|
+
try {
|
|
295
|
+
return await Promise.race([run(), timeoutPromise]);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
if (error instanceof Error && error.message.includes("structured-output timed out after")) {
|
|
299
|
+
try {
|
|
300
|
+
await onTimeout();
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
// best-effort abort
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
throw error;
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
if (timer !== undefined)
|
|
310
|
+
clearTimeout(timer);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function readSdkData(response, label) {
|
|
314
|
+
const result = response;
|
|
315
|
+
if (result?.error !== undefined) {
|
|
316
|
+
const status = result.response ? ` (${responseStatus(result.response)})` : "";
|
|
317
|
+
const responseUrl = result.response?.url || result.request?.url;
|
|
318
|
+
const url = responseUrl ? diagnosticUrl(responseUrl) : "the authenticated client";
|
|
319
|
+
throw new Error(`omms: opencode ${label} failed at ${url}${status}: <redacted response body>`);
|
|
320
|
+
}
|
|
321
|
+
if (result?.data === undefined) {
|
|
322
|
+
throw new Error(`omms: opencode ${label} returned no response data`);
|
|
323
|
+
}
|
|
324
|
+
return result.data;
|
|
325
|
+
}
|
|
326
|
+
function stripTrailingSlash(url) {
|
|
327
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
328
|
+
}
|
|
329
|
+
function buildQuery(directory) {
|
|
330
|
+
if (!directory)
|
|
331
|
+
return "";
|
|
332
|
+
return `?directory=${encodeURIComponent(directory)}`;
|
|
333
|
+
}
|
|
334
|
+
async function fetchJson(endpoint, init) {
|
|
335
|
+
let res;
|
|
336
|
+
try {
|
|
337
|
+
res = await activeFetch()(new Request(endpoint.url, init));
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
+
throw new Error(`omms: failed to fetch ${endpoint.label} at ${diagnosticUrl(endpoint.url)}: ${message}`, { cause: error });
|
|
342
|
+
}
|
|
343
|
+
return readJson(res, endpoint);
|
|
344
|
+
}
|
|
345
|
+
async function createSession(base, directory) {
|
|
346
|
+
const url = `${base}/session${buildQuery(directory)}`;
|
|
347
|
+
const body = await fetchJson({ label: "POST /session", url }, {
|
|
348
|
+
method: "POST",
|
|
349
|
+
headers: { "Content-Type": "application/json" },
|
|
350
|
+
body: JSON.stringify(sessionCreateBody()),
|
|
351
|
+
});
|
|
352
|
+
if (!body.id) {
|
|
353
|
+
throw new Error("omms: session.create returned no session id; cannot generate structured output");
|
|
354
|
+
}
|
|
355
|
+
trackInternalCaptureSession(body.id);
|
|
356
|
+
return body.id;
|
|
357
|
+
}
|
|
358
|
+
function formatAssistantError(error) {
|
|
359
|
+
if (!error.data)
|
|
360
|
+
return error.name;
|
|
361
|
+
const details = safeAssistantErrorDetails(error.data);
|
|
362
|
+
if (!error.data.message)
|
|
363
|
+
return details;
|
|
364
|
+
return details ? `${error.data.message}; ${details}` : error.data.message;
|
|
365
|
+
}
|
|
366
|
+
function safeAssistantErrorDetails(data) {
|
|
367
|
+
const safeFields = {};
|
|
368
|
+
for (const key of ["statusCode", "providerID", "modelID"]) {
|
|
369
|
+
const value = data[key];
|
|
370
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
371
|
+
safeFields[key] = value;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const entries = Object.entries(safeFields);
|
|
375
|
+
if (entries.length === 0)
|
|
376
|
+
return "";
|
|
377
|
+
return `details=${JSON.stringify(Object.fromEntries(entries))}`;
|
|
378
|
+
}
|
|
379
|
+
async function promptSession(base, args) {
|
|
380
|
+
const url = `${base}/session/${encodeURIComponent(args.sessionID)}/message${buildQuery(args.directory)}`;
|
|
381
|
+
const body = sessionPromptFields(args);
|
|
382
|
+
const data = await fetchJson({ label: "POST /session/{id}/message", url }, {
|
|
383
|
+
method: "POST",
|
|
384
|
+
headers: { "Content-Type": "application/json" },
|
|
385
|
+
body: JSON.stringify(body),
|
|
386
|
+
});
|
|
387
|
+
if (!data.info) {
|
|
388
|
+
throw new Error("omms: prompt response missing `info`");
|
|
389
|
+
}
|
|
390
|
+
return data.info;
|
|
391
|
+
}
|
|
392
|
+
async function abortSession(base, sessionID, directory) {
|
|
393
|
+
const url = `${base}/session/${encodeURIComponent(sessionID)}/abort${buildQuery(directory)}`;
|
|
394
|
+
try {
|
|
395
|
+
await activeFetch()(new Request(url, { method: "POST" }));
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// best-effort
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async function deleteSession(base, sessionID, directory) {
|
|
402
|
+
const url = `${base}/session/${encodeURIComponent(sessionID)}${buildQuery(directory)}`;
|
|
403
|
+
let res;
|
|
404
|
+
try {
|
|
405
|
+
res = await activeFetch()(new Request(url, { method: "DELETE" }));
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
409
|
+
throw new Error(`omms: failed to fetch DELETE /session/{id} at ${diagnosticUrl(url)}: ${message}`, { cause: error });
|
|
410
|
+
}
|
|
411
|
+
// DELETE /session/:id returns boolean. We only care that it ran; failures
|
|
412
|
+
// are swallowed at the call site.
|
|
413
|
+
if (!res.ok) {
|
|
414
|
+
throw new Error(`omms: opencode DELETE /session/{id} failed at ${diagnosticUrl(url)} (${responseStatus(res)})`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function activeFetch() {
|
|
418
|
+
return _hostFetch ?? globalThis.fetch;
|
|
419
|
+
}
|