@love-moon/ai-sdk 0.3.1 → 0.4.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/CHANGELOG.md +18 -0
- package/dist/built-in-backends.d.ts +1 -0
- package/dist/built-in-backends.js +6 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.js +103 -1
- package/dist/external-provider-registry.js +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/manager/account.d.ts +6 -0
- package/dist/manager/account.js +121 -0
- package/dist/manager/auth-parser.d.ts +27 -0
- package/dist/manager/auth-parser.js +54 -0
- package/dist/manager/config.d.ts +6 -0
- package/dist/manager/config.js +32 -0
- package/dist/manager/index.d.ts +12 -0
- package/dist/manager/index.js +11 -0
- package/dist/manager/install.d.ts +9 -0
- package/dist/manager/install.js +117 -0
- package/dist/manager/manager.d.ts +51 -0
- package/dist/manager/manager.js +105 -0
- package/dist/manager/network.d.ts +8 -0
- package/dist/manager/network.js +46 -0
- package/dist/manager/paths.d.ts +6 -0
- package/dist/manager/paths.js +16 -0
- package/dist/manager/quota/cache.d.ts +9 -0
- package/dist/manager/quota/cache.js +33 -0
- package/dist/manager/quota/claude.d.ts +19 -0
- package/dist/manager/quota/claude.js +193 -0
- package/dist/manager/quota/codex.d.ts +27 -0
- package/dist/manager/quota/codex.js +182 -0
- package/dist/manager/quota/copilot.d.ts +64 -0
- package/dist/manager/quota/copilot.js +718 -0
- package/dist/manager/quota/external.d.ts +29 -0
- package/dist/manager/quota/external.js +176 -0
- package/dist/manager/quota/headers.d.ts +5 -0
- package/dist/manager/quota/headers.js +29 -0
- package/dist/manager/quota/kimi.d.ts +24 -0
- package/dist/manager/quota/kimi.js +230 -0
- package/dist/manager/types.d.ts +166 -0
- package/dist/manager/types.js +1 -0
- package/dist/providers/chat-web-session.d.ts +218 -0
- package/dist/providers/chat-web-session.js +584 -0
- package/dist/providers/claude-agent-sdk-session.d.ts +35 -1
- package/dist/providers/claude-agent-sdk-session.js +109 -1
- package/dist/providers/codex-app-server-session.d.ts +107 -0
- package/dist/providers/codex-app-server-session.js +479 -9
- package/dist/providers/copilot-sdk-session.d.ts +9 -1
- package/dist/providers/copilot-sdk-session.js +48 -0
- package/dist/resume/chat-web.d.ts +20 -0
- package/dist/resume/chat-web.js +44 -0
- package/dist/resume/index.js +2 -0
- package/dist/session-factory.d.ts +3 -1
- package/dist/session-factory.js +17 -4
- package/dist/shared.d.ts +159 -0
- package/dist/shared.js +111 -0
- package/dist/transports/codex-app-server-transport.d.ts +1 -0
- package/dist/transports/codex-app-server-transport.js +45 -1
- package/dist/worker.js +19 -5
- package/package.json +10 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chat-web's "session" is a Chromium browser context, not a conversation id.
|
|
3
|
+
* There is no native resume primitive: closing and reopening produces a
|
|
4
|
+
* fresh ChatSession with a new in-memory chat. We satisfy ai-sdk's resume
|
|
5
|
+
* contract by surfacing a synthetic session id and a "best effort" context,
|
|
6
|
+
* but downstream callers should treat resume here as a no-op.
|
|
7
|
+
*/
|
|
8
|
+
export function buildCliArgs(sessionId: any): string[];
|
|
9
|
+
export function findSessionPath(): Promise<null>;
|
|
10
|
+
export function resolveResumeContext(sessionId: any, options?: {}): Promise<{
|
|
11
|
+
provider: any;
|
|
12
|
+
sessionId: any;
|
|
13
|
+
sessionPath: null;
|
|
14
|
+
cwd: any;
|
|
15
|
+
debugMetadata: {
|
|
16
|
+
cwdSource: any;
|
|
17
|
+
sessionPath: null;
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
export const BACKEND: "chat-web";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { buildResumeContext, normalizeSessionId } from "./shared.js";
|
|
2
|
+
export const BACKEND = "chat-web";
|
|
3
|
+
/**
|
|
4
|
+
* chat-web's "session" is a Chromium browser context, not a conversation id.
|
|
5
|
+
* There is no native resume primitive: closing and reopening produces a
|
|
6
|
+
* fresh ChatSession with a new in-memory chat. We satisfy ai-sdk's resume
|
|
7
|
+
* contract by surfacing a synthetic session id and a "best effort" context,
|
|
8
|
+
* but downstream callers should treat resume here as a no-op.
|
|
9
|
+
*/
|
|
10
|
+
export function buildCliArgs(sessionId) {
|
|
11
|
+
// chat-web has no CLI to pass these args to (it's an in-process SDK), but
|
|
12
|
+
// the resume contract requires every backend to return a non-empty arg
|
|
13
|
+
// vector so downstream resume dispatch stays uniform. We emit a stable,
|
|
14
|
+
// recognisable token; consumers should ignore it for chat-web.
|
|
15
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
16
|
+
if (!normalizedSessionId) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
return [`--resume-session-id=${normalizedSessionId}`];
|
|
20
|
+
}
|
|
21
|
+
export async function findSessionPath() {
|
|
22
|
+
// No on-disk JSONL session file — the persistent state lives in the
|
|
23
|
+
// browser profile dir, but that's not what callers expect from this hook.
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export async function resolveResumeContext(sessionId, options = {}) {
|
|
27
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
28
|
+
if (!normalizedSessionId) {
|
|
29
|
+
throw new Error("--resume requires a session id");
|
|
30
|
+
}
|
|
31
|
+
const cwd = typeof options?.cwd === "string" && options.cwd.trim()
|
|
32
|
+
? options.cwd.trim()
|
|
33
|
+
: process.cwd();
|
|
34
|
+
return buildResumeContext({
|
|
35
|
+
provider: "chat-web",
|
|
36
|
+
sessionId: normalizedSessionId,
|
|
37
|
+
sessionPath: null,
|
|
38
|
+
cwd,
|
|
39
|
+
cwdSource: "process_cwd",
|
|
40
|
+
extraDebug: {
|
|
41
|
+
note: "chat-web does not support cross-process conversation resume; a fresh ChatSession will be opened.",
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
package/dist/resume/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BUILT_IN_BACKENDS, getBuiltInBackendEntry, normalizeBuiltInBackend, } from "../built-in-backends.js";
|
|
2
2
|
import { getExternalProviderDescriptor, resolveExternalBackend, } from "../external-provider-registry.js";
|
|
3
|
+
import * as chatWeb from "./chat-web.js";
|
|
3
4
|
import * as claude from "./claude.js";
|
|
4
5
|
import * as codex from "./codex.js";
|
|
5
6
|
import * as copilot from "./copilot.js";
|
|
@@ -25,6 +26,7 @@ const RESUME_MODULES_BY_BACKEND = new Map([
|
|
|
25
26
|
["copilot", copilot],
|
|
26
27
|
["kimi", kimi],
|
|
27
28
|
["opencode", opencode],
|
|
29
|
+
["chat-web", chatWeb],
|
|
28
30
|
]);
|
|
29
31
|
// Sanity-check at module load:
|
|
30
32
|
// (1) Every registered resume module must point at a real built-in backend.
|
|
@@ -10,7 +10,9 @@ export const COPILOT_PROVIDER_VARIANT: "copilot-sdk";
|
|
|
10
10
|
export const KIMI_PROVIDER_VARIANT: "kimi-cli-wire";
|
|
11
11
|
export const KIMI_PRINT_PROVIDER_VARIANT: "kimi-cli-print";
|
|
12
12
|
export const OPENCODE_PROVIDER_VARIANT: "opencode-sdk";
|
|
13
|
+
export const CHAT_WEB_PROVIDER_VARIANT: "chat-web-session";
|
|
13
14
|
import { BUILT_IN_BACKENDS } from "./built-in-backends.js";
|
|
15
|
+
import { ChatWebSession } from "./providers/chat-web-session.js";
|
|
14
16
|
import { CodexAppServerSession } from "./providers/codex-app-server-session.js";
|
|
15
17
|
import { CodexExecSession } from "./providers/codex-exec-session.js";
|
|
16
18
|
import { ClaudeAgentSdkSession } from "./providers/claude-agent-sdk-session.js";
|
|
@@ -18,4 +20,4 @@ import { CopilotSdkSession } from "./providers/copilot-sdk-session.js";
|
|
|
18
20
|
import { KimiCliSession } from "./providers/kimi-cli-session.js";
|
|
19
21
|
import { KimiPrintSession } from "./providers/kimi-print-session.js";
|
|
20
22
|
import { OpencodeSdkSession } from "./providers/opencode-sdk-session.js";
|
|
21
|
-
export { BUILT_IN_BACKENDS, CodexAppServerSession, CodexExecSession, ClaudeAgentSdkSession, CopilotSdkSession, KimiCliSession, KimiPrintSession, OpencodeSdkSession };
|
|
23
|
+
export { BUILT_IN_BACKENDS, ChatWebSession, CodexAppServerSession, CodexExecSession, ClaudeAgentSdkSession, CopilotSdkSession, KimiCliSession, KimiPrintSession, OpencodeSdkSession };
|
package/dist/session-factory.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { BUILT_IN_BACKENDS, CLAUDE_AGENT_SDK_VARIANT, CODEX_APP_SERVER_VARIANT, CODEX_EXEC_VARIANT, COPILOT_SDK_VARIANT, KIMI_CLI_PRINT_VARIANT, KIMI_CLI_WIRE_VARIANT, OPENCODE_SDK_VARIANT, getBuiltInBackendEntry, isBuiltInBackend, listBuiltInBackends, normalizeBuiltInBackend, } from "./built-in-backends.js";
|
|
1
|
+
import { BUILT_IN_BACKENDS, CHAT_WEB_SESSION_VARIANT, CLAUDE_AGENT_SDK_VARIANT, CODEX_APP_SERVER_VARIANT, CODEX_EXEC_VARIANT, COPILOT_SDK_VARIANT, KIMI_CLI_PRINT_VARIANT, KIMI_CLI_WIRE_VARIANT, OPENCODE_SDK_VARIANT, getBuiltInBackendEntry, isBuiltInBackend, listBuiltInBackends, normalizeBuiltInBackend, } from "./built-in-backends.js";
|
|
2
|
+
import { ChatWebSession } from "./providers/chat-web-session.js";
|
|
2
3
|
import { CodexAppServerSession } from "./providers/codex-app-server-session.js";
|
|
3
4
|
import { CodexExecSession } from "./providers/codex-exec-session.js";
|
|
4
5
|
import { ClaudeAgentSdkSession } from "./providers/claude-agent-sdk-session.js";
|
|
@@ -14,12 +15,22 @@ export const COPILOT_PROVIDER_VARIANT = COPILOT_SDK_VARIANT;
|
|
|
14
15
|
export const KIMI_PROVIDER_VARIANT = KIMI_CLI_WIRE_VARIANT;
|
|
15
16
|
export const KIMI_PRINT_PROVIDER_VARIANT = KIMI_CLI_PRINT_VARIANT;
|
|
16
17
|
export const OPENCODE_PROVIDER_VARIANT = OPENCODE_SDK_VARIANT;
|
|
18
|
+
export const CHAT_WEB_PROVIDER_VARIANT = CHAT_WEB_SESSION_VARIANT;
|
|
17
19
|
const SESSION_FACTORIES_BY_BACKEND = new Map([
|
|
18
20
|
[
|
|
19
21
|
"codex",
|
|
20
|
-
(backend, options) =>
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
(backend, options) => {
|
|
23
|
+
// Goal mode requires the app-server transport (codex-exec has no
|
|
24
|
+
// `thread/goal/set` JSON-RPC). When goalMode is requested we always
|
|
25
|
+
// pick the app-server variant, even if a structured-output preference
|
|
26
|
+
// would otherwise route to codex-exec.
|
|
27
|
+
if (options?.goalMode === true) {
|
|
28
|
+
return new CodexAppServerSession(backend, options);
|
|
29
|
+
}
|
|
30
|
+
return hasStructuredOutputPreference(options)
|
|
31
|
+
? new CodexExecSession(backend, options)
|
|
32
|
+
: new CodexAppServerSession(backend, options);
|
|
33
|
+
},
|
|
23
34
|
],
|
|
24
35
|
["claude", (backend, options) => new ClaudeAgentSdkSession(backend, options)],
|
|
25
36
|
["copilot", (backend, options) => new CopilotSdkSession(backend, options)],
|
|
@@ -30,6 +41,7 @@ const SESSION_FACTORIES_BY_BACKEND = new Map([
|
|
|
30
41
|
: new KimiCliSession(backend, options),
|
|
31
42
|
],
|
|
32
43
|
["opencode", (backend, options) => new OpencodeSdkSession(backend, options)],
|
|
44
|
+
["chat-web", (backend, options) => new ChatWebSession(backend, options)],
|
|
33
45
|
]);
|
|
34
46
|
function hasStructuredOutputPreference(options = {}) {
|
|
35
47
|
if (!options || typeof options !== "object") {
|
|
@@ -106,6 +118,7 @@ export async function createLocalAiSession(backend, options = {}) {
|
|
|
106
118
|
return await descriptor.createSession(normalized, options);
|
|
107
119
|
}
|
|
108
120
|
export { BUILT_IN_BACKENDS };
|
|
121
|
+
export { ChatWebSession };
|
|
109
122
|
export { CodexAppServerSession };
|
|
110
123
|
export { CodexExecSession };
|
|
111
124
|
export { ClaudeAgentSdkSession };
|
package/dist/shared.d.ts
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {unknown} value
|
|
3
|
+
* @returns {value is GoalStatus}
|
|
4
|
+
*/
|
|
5
|
+
export function isGoalStatus(value: unknown): value is GoalStatus;
|
|
6
|
+
/**
|
|
7
|
+
* @param {unknown} value
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
export function isTerminalGoalStatus(value: unknown): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Resolve a session's capability snapshot, falling back to
|
|
13
|
+
* {@link DEFAULT_SESSION_CAPABILITIES}.
|
|
14
|
+
*
|
|
15
|
+
* @param {unknown} session
|
|
16
|
+
* @returns {SessionCapabilities}
|
|
17
|
+
*/
|
|
18
|
+
export function resolveSessionCapabilities(session: unknown): SessionCapabilities;
|
|
1
19
|
export function normalizeLogger(logger: any): any;
|
|
2
20
|
export function emitLog(logger: any, message: any): void;
|
|
3
21
|
export function truncateText(value: any, maxLen?: number): string;
|
|
@@ -16,3 +34,144 @@ export function proxyToEnv(envConfig: any): {};
|
|
|
16
34
|
export function withoutCopilotGithubTokenEnv(env: any): any;
|
|
17
35
|
export function serializeError(error: any): any;
|
|
18
36
|
export function reviveError(payload: any): Error;
|
|
37
|
+
/**
|
|
38
|
+
* Goal-mode capability typedefs shared by AI SDK providers that implement
|
|
39
|
+
* the optional {@link runGoal} method.
|
|
40
|
+
*
|
|
41
|
+
* Callers should treat goal support as opt-in: detect with
|
|
42
|
+
* `typeof session.runGoal === "function"`. When unsupported, do NOT silently
|
|
43
|
+
* fall back to {@link runTurn} — surface a clear error instead.
|
|
44
|
+
*
|
|
45
|
+
* Session creation may also accept `{ goalMode: true }` so the underlying
|
|
46
|
+
* transport can adjust its spawn arguments (e.g. Codex app-server requires
|
|
47
|
+
* `--enable goals` at boot time; dynamic enablement is not supported).
|
|
48
|
+
*
|
|
49
|
+
* @typedef {"active"|"paused"|"blocked"|"usageLimited"|"budgetLimited"|"complete"} GoalStatus
|
|
50
|
+
*
|
|
51
|
+
* @typedef {Object} GoalRequest
|
|
52
|
+
* @property {string} objective
|
|
53
|
+
* @property {number|null} [tokenBudget]
|
|
54
|
+
* @property {{ type: "issue"|"manual", issueId?: string, taskId?: string }} [source]
|
|
55
|
+
*
|
|
56
|
+
* @typedef {Object} GoalState
|
|
57
|
+
* @property {string} [id]
|
|
58
|
+
* @property {string} [threadId]
|
|
59
|
+
* @property {string} objective
|
|
60
|
+
* @property {GoalStatus} status
|
|
61
|
+
* @property {number|null} [tokenBudget]
|
|
62
|
+
*
|
|
63
|
+
* @typedef {Object} GoalResult
|
|
64
|
+
* @property {string} text
|
|
65
|
+
* @property {GoalState} goal
|
|
66
|
+
* @property {unknown} [usage]
|
|
67
|
+
* @property {Record<string, unknown>} [metadata]
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* Runtime list of valid {@link GoalStatus} values. Useful for validation.
|
|
71
|
+
* Terminal statuses are everything except "active" and "paused".
|
|
72
|
+
* @type {ReadonlyArray<GoalStatus>}
|
|
73
|
+
*/
|
|
74
|
+
export const GOAL_STATUSES: ReadonlyArray<GoalStatus>;
|
|
75
|
+
/**
|
|
76
|
+
* Terminal goal statuses (the goal will not produce further updates).
|
|
77
|
+
* @type {ReadonlyArray<GoalStatus>}
|
|
78
|
+
*/
|
|
79
|
+
export const TERMINAL_GOAL_STATUSES: ReadonlyArray<GoalStatus>;
|
|
80
|
+
/**
|
|
81
|
+
* Session capability flags advertised in {@link SessionSnapshot.capabilities}.
|
|
82
|
+
*
|
|
83
|
+
* Providers may declare optional features (e.g. `goal`) so that callers can
|
|
84
|
+
* detect support without reflecting on method names (which is unreliable when
|
|
85
|
+
* a wrapping proxy unconditionally forwards every method).
|
|
86
|
+
*
|
|
87
|
+
* @typedef {Object} SessionCapabilities
|
|
88
|
+
* @property {boolean} [goal]
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* Default capabilities assigned to providers that do not opt into any optional
|
|
92
|
+
* feature. Treat the returned object as read-only; clone before mutating.
|
|
93
|
+
*
|
|
94
|
+
* @type {Readonly<SessionCapabilities>}
|
|
95
|
+
*/
|
|
96
|
+
export const DEFAULT_SESSION_CAPABILITIES: Readonly<SessionCapabilities>;
|
|
97
|
+
/**
|
|
98
|
+
* Goal-mode capability typedefs shared by AI SDK providers that implement
|
|
99
|
+
* the optional {@link runGoal} method.
|
|
100
|
+
*
|
|
101
|
+
* Callers should treat goal support as opt-in: detect with
|
|
102
|
+
* `typeof session.runGoal === "function"`. When unsupported, do NOT silently
|
|
103
|
+
* fall back to {@link runTurn} — surface a clear error instead.
|
|
104
|
+
*
|
|
105
|
+
* Session creation may also accept `{ goalMode: true }` so the underlying
|
|
106
|
+
* transport can adjust its spawn arguments (e.g. Codex app-server requires
|
|
107
|
+
* `--enable goals` at boot time; dynamic enablement is not supported).
|
|
108
|
+
*/
|
|
109
|
+
export type GoalStatus = "active" | "paused" | "blocked" | "usageLimited" | "budgetLimited" | "complete";
|
|
110
|
+
/**
|
|
111
|
+
* Goal-mode capability typedefs shared by AI SDK providers that implement
|
|
112
|
+
* the optional {@link runGoal} method.
|
|
113
|
+
*
|
|
114
|
+
* Callers should treat goal support as opt-in: detect with
|
|
115
|
+
* `typeof session.runGoal === "function"`. When unsupported, do NOT silently
|
|
116
|
+
* fall back to {@link runTurn} — surface a clear error instead.
|
|
117
|
+
*
|
|
118
|
+
* Session creation may also accept `{ goalMode: true }` so the underlying
|
|
119
|
+
* transport can adjust its spawn arguments (e.g. Codex app-server requires
|
|
120
|
+
* `--enable goals` at boot time; dynamic enablement is not supported).
|
|
121
|
+
*/
|
|
122
|
+
export type GoalRequest = {
|
|
123
|
+
objective: string;
|
|
124
|
+
tokenBudget?: number | null | undefined;
|
|
125
|
+
source?: {
|
|
126
|
+
type: "issue" | "manual";
|
|
127
|
+
issueId?: string;
|
|
128
|
+
taskId?: string;
|
|
129
|
+
} | undefined;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Goal-mode capability typedefs shared by AI SDK providers that implement
|
|
133
|
+
* the optional {@link runGoal} method.
|
|
134
|
+
*
|
|
135
|
+
* Callers should treat goal support as opt-in: detect with
|
|
136
|
+
* `typeof session.runGoal === "function"`. When unsupported, do NOT silently
|
|
137
|
+
* fall back to {@link runTurn} — surface a clear error instead.
|
|
138
|
+
*
|
|
139
|
+
* Session creation may also accept `{ goalMode: true }` so the underlying
|
|
140
|
+
* transport can adjust its spawn arguments (e.g. Codex app-server requires
|
|
141
|
+
* `--enable goals` at boot time; dynamic enablement is not supported).
|
|
142
|
+
*/
|
|
143
|
+
export type GoalState = {
|
|
144
|
+
id?: string | undefined;
|
|
145
|
+
threadId?: string | undefined;
|
|
146
|
+
objective: string;
|
|
147
|
+
status: GoalStatus;
|
|
148
|
+
tokenBudget?: number | null | undefined;
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Goal-mode capability typedefs shared by AI SDK providers that implement
|
|
152
|
+
* the optional {@link runGoal} method.
|
|
153
|
+
*
|
|
154
|
+
* Callers should treat goal support as opt-in: detect with
|
|
155
|
+
* `typeof session.runGoal === "function"`. When unsupported, do NOT silently
|
|
156
|
+
* fall back to {@link runTurn} — surface a clear error instead.
|
|
157
|
+
*
|
|
158
|
+
* Session creation may also accept `{ goalMode: true }` so the underlying
|
|
159
|
+
* transport can adjust its spawn arguments (e.g. Codex app-server requires
|
|
160
|
+
* `--enable goals` at boot time; dynamic enablement is not supported).
|
|
161
|
+
*/
|
|
162
|
+
export type GoalResult = {
|
|
163
|
+
text: string;
|
|
164
|
+
goal: GoalState;
|
|
165
|
+
usage?: unknown;
|
|
166
|
+
metadata?: Record<string, unknown> | undefined;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Session capability flags advertised in {@link SessionSnapshot.capabilities}.
|
|
170
|
+
*
|
|
171
|
+
* Providers may declare optional features (e.g. `goal`) so that callers can
|
|
172
|
+
* detect support without reflecting on method names (which is unreliable when
|
|
173
|
+
* a wrapping proxy unconditionally forwards every method).
|
|
174
|
+
*/
|
|
175
|
+
export type SessionCapabilities = {
|
|
176
|
+
goal?: boolean | undefined;
|
|
177
|
+
};
|
package/dist/shared.js
CHANGED
|
@@ -2,6 +2,117 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import yaml from "js-yaml";
|
|
5
|
+
/**
|
|
6
|
+
* Goal-mode capability typedefs shared by AI SDK providers that implement
|
|
7
|
+
* the optional {@link runGoal} method.
|
|
8
|
+
*
|
|
9
|
+
* Callers should treat goal support as opt-in: detect with
|
|
10
|
+
* `typeof session.runGoal === "function"`. When unsupported, do NOT silently
|
|
11
|
+
* fall back to {@link runTurn} — surface a clear error instead.
|
|
12
|
+
*
|
|
13
|
+
* Session creation may also accept `{ goalMode: true }` so the underlying
|
|
14
|
+
* transport can adjust its spawn arguments (e.g. Codex app-server requires
|
|
15
|
+
* `--enable goals` at boot time; dynamic enablement is not supported).
|
|
16
|
+
*
|
|
17
|
+
* @typedef {"active"|"paused"|"blocked"|"usageLimited"|"budgetLimited"|"complete"} GoalStatus
|
|
18
|
+
*
|
|
19
|
+
* @typedef {Object} GoalRequest
|
|
20
|
+
* @property {string} objective
|
|
21
|
+
* @property {number|null} [tokenBudget]
|
|
22
|
+
* @property {{ type: "issue"|"manual", issueId?: string, taskId?: string }} [source]
|
|
23
|
+
*
|
|
24
|
+
* @typedef {Object} GoalState
|
|
25
|
+
* @property {string} [id]
|
|
26
|
+
* @property {string} [threadId]
|
|
27
|
+
* @property {string} objective
|
|
28
|
+
* @property {GoalStatus} status
|
|
29
|
+
* @property {number|null} [tokenBudget]
|
|
30
|
+
*
|
|
31
|
+
* @typedef {Object} GoalResult
|
|
32
|
+
* @property {string} text
|
|
33
|
+
* @property {GoalState} goal
|
|
34
|
+
* @property {unknown} [usage]
|
|
35
|
+
* @property {Record<string, unknown>} [metadata]
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Runtime list of valid {@link GoalStatus} values. Useful for validation.
|
|
39
|
+
* Terminal statuses are everything except "active" and "paused".
|
|
40
|
+
* @type {ReadonlyArray<GoalStatus>}
|
|
41
|
+
*/
|
|
42
|
+
export const GOAL_STATUSES = Object.freeze([
|
|
43
|
+
"active",
|
|
44
|
+
"paused",
|
|
45
|
+
"blocked",
|
|
46
|
+
"usageLimited",
|
|
47
|
+
"budgetLimited",
|
|
48
|
+
"complete",
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Terminal goal statuses (the goal will not produce further updates).
|
|
52
|
+
* @type {ReadonlyArray<GoalStatus>}
|
|
53
|
+
*/
|
|
54
|
+
export const TERMINAL_GOAL_STATUSES = Object.freeze([
|
|
55
|
+
"blocked",
|
|
56
|
+
"usageLimited",
|
|
57
|
+
"budgetLimited",
|
|
58
|
+
"complete",
|
|
59
|
+
]);
|
|
60
|
+
/**
|
|
61
|
+
* @param {unknown} value
|
|
62
|
+
* @returns {value is GoalStatus}
|
|
63
|
+
*/
|
|
64
|
+
export function isGoalStatus(value) {
|
|
65
|
+
return typeof value === "string" && GOAL_STATUSES.includes(/** @type {GoalStatus} */ (value));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* @param {unknown} value
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
export function isTerminalGoalStatus(value) {
|
|
72
|
+
return typeof value === "string" && TERMINAL_GOAL_STATUSES.includes(/** @type {GoalStatus} */ (value));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Session capability flags advertised in {@link SessionSnapshot.capabilities}.
|
|
76
|
+
*
|
|
77
|
+
* Providers may declare optional features (e.g. `goal`) so that callers can
|
|
78
|
+
* detect support without reflecting on method names (which is unreliable when
|
|
79
|
+
* a wrapping proxy unconditionally forwards every method).
|
|
80
|
+
*
|
|
81
|
+
* @typedef {Object} SessionCapabilities
|
|
82
|
+
* @property {boolean} [goal]
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* Default capabilities assigned to providers that do not opt into any optional
|
|
86
|
+
* feature. Treat the returned object as read-only; clone before mutating.
|
|
87
|
+
*
|
|
88
|
+
* @type {Readonly<SessionCapabilities>}
|
|
89
|
+
*/
|
|
90
|
+
export const DEFAULT_SESSION_CAPABILITIES = Object.freeze({ goal: false });
|
|
91
|
+
/**
|
|
92
|
+
* Resolve a session's capability snapshot, falling back to
|
|
93
|
+
* {@link DEFAULT_SESSION_CAPABILITIES}.
|
|
94
|
+
*
|
|
95
|
+
* @param {unknown} session
|
|
96
|
+
* @returns {SessionCapabilities}
|
|
97
|
+
*/
|
|
98
|
+
export function resolveSessionCapabilities(session) {
|
|
99
|
+
if (session && typeof session === "object") {
|
|
100
|
+
const obj = /** @type {Record<string, unknown>} */ (session);
|
|
101
|
+
if (typeof obj.getCapabilities === "function") {
|
|
102
|
+
const raw = /** @type {() => unknown} */ (obj.getCapabilities).call(session);
|
|
103
|
+
if (raw && typeof raw === "object") {
|
|
104
|
+
return { ...DEFAULT_SESSION_CAPABILITIES, ...raw };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const constructor = /** @type {{ capabilities?: unknown }} */ ((session.constructor && typeof session.constructor === "function"
|
|
108
|
+
? session.constructor
|
|
109
|
+
: null) || {});
|
|
110
|
+
if (constructor && constructor.capabilities && typeof constructor.capabilities === "object") {
|
|
111
|
+
return { ...DEFAULT_SESSION_CAPABILITIES, ...constructor.capabilities };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { ...DEFAULT_SESSION_CAPABILITIES };
|
|
115
|
+
}
|
|
5
116
|
export function normalizeLogger(logger) {
|
|
6
117
|
if (typeof logger === "function") {
|
|
7
118
|
return { log: logger };
|
|
@@ -3,6 +3,49 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import readline from "node:readline";
|
|
4
4
|
import { emitLog, normalizeLogger, parseCommandParts, sanitizeForLog, serializeError, } from "../shared.js";
|
|
5
5
|
const DEFAULT_CODEX_APP_SERVER_COMMAND = "codex app-server --listen stdio://";
|
|
6
|
+
/**
|
|
7
|
+
* Add `--enable goals` to the codex app-server spawn args when goal mode is
|
|
8
|
+
* requested. Dynamic enablement via `experimentalFeature/enablement/set` is
|
|
9
|
+
* NOT supported by codex, so the feature must be enabled at boot. Idempotent:
|
|
10
|
+
* does not double-add when the user already passed any of the supported
|
|
11
|
+
* spellings (`--enable goals`, `--enable=goals`, `--enable=goals,foo`) in the
|
|
12
|
+
* command line.
|
|
13
|
+
*
|
|
14
|
+
* @param {string[]} args
|
|
15
|
+
* @param {boolean} enableGoals
|
|
16
|
+
* @returns {string[]}
|
|
17
|
+
*/
|
|
18
|
+
function injectEnableGoalsArgs(args, enableGoals) {
|
|
19
|
+
const normalized = Array.isArray(args) ? [...args] : [];
|
|
20
|
+
if (!enableGoals) {
|
|
21
|
+
return normalized;
|
|
22
|
+
}
|
|
23
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
24
|
+
const token = normalized[index];
|
|
25
|
+
if (typeof token !== "string") {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// Two-token form: ["--enable", "goals"]
|
|
29
|
+
if (token === "--enable" && normalized[index + 1] === "goals") {
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
// Equals form: "--enable=goals" or comma list "--enable=goals,foo"
|
|
33
|
+
if (token.startsWith("--enable=")) {
|
|
34
|
+
const value = token.slice("--enable=".length);
|
|
35
|
+
const features = value
|
|
36
|
+
.split(",")
|
|
37
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
if (features.includes("goals")) {
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const listenIndex = normalized.indexOf("--listen");
|
|
45
|
+
const insertAt = listenIndex >= 0 ? listenIndex : normalized.length;
|
|
46
|
+
normalized.splice(insertAt, 0, "--enable", "goals");
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
6
49
|
function createRpcError(payload) {
|
|
7
50
|
const message = String(payload?.message || payload?.error?.message || "Codex app-server request failed");
|
|
8
51
|
const error = new Error(message);
|
|
@@ -34,7 +77,8 @@ export class CodexAppServerTransport extends EventEmitter {
|
|
|
34
77
|
throw new Error("Invalid codex app-server command");
|
|
35
78
|
}
|
|
36
79
|
this.command = command;
|
|
37
|
-
this.
|
|
80
|
+
this.enableGoals = options.enableGoals === true;
|
|
81
|
+
this.args = injectEnableGoalsArgs(args, this.enableGoals);
|
|
38
82
|
this.env = options.env && typeof options.env === "object" ? { ...options.env } : {};
|
|
39
83
|
this.ignoreCodexApiKey = options.ignoreCodexApiKey === true;
|
|
40
84
|
this.child = null;
|
package/dist/worker.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import readline from "node:readline";
|
|
3
3
|
import { createLocalAiSession } from "./session-factory.js";
|
|
4
|
-
import { serializeError } from "./shared.js";
|
|
4
|
+
import { DEFAULT_SESSION_CAPABILITIES, resolveSessionCapabilities, serializeError, } from "./shared.js";
|
|
5
|
+
function buildReadySnapshot(aiSession) {
|
|
6
|
+
const snapshot = typeof aiSession?.getSnapshot === "function" ? aiSession.getSnapshot() : null;
|
|
7
|
+
const base = snapshot && typeof snapshot === "object" ? { ...snapshot } : {};
|
|
8
|
+
// Always include a capabilities field so the parent process can do the
|
|
9
|
+
// declarative `capabilities.goal === false` short-circuit without needing
|
|
10
|
+
// to inspect method names through the IPC boundary.
|
|
11
|
+
if (!base.capabilities || typeof base.capabilities !== "object") {
|
|
12
|
+
base.capabilities = resolveSessionCapabilities(aiSession);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
base.capabilities = { ...DEFAULT_SESSION_CAPABILITIES, ...base.capabilities };
|
|
16
|
+
}
|
|
17
|
+
return base;
|
|
18
|
+
}
|
|
5
19
|
let session = null;
|
|
6
20
|
let sessionCreated = false;
|
|
7
21
|
function send(payload) {
|
|
@@ -48,7 +62,7 @@ async function handleCreate(message) {
|
|
|
48
62
|
attachSessionEvents(session);
|
|
49
63
|
send({
|
|
50
64
|
type: "ready",
|
|
51
|
-
snapshot:
|
|
65
|
+
snapshot: buildReadySnapshot(session),
|
|
52
66
|
workerPid: process.pid,
|
|
53
67
|
workerProcessPid: process.pid,
|
|
54
68
|
});
|
|
@@ -62,8 +76,8 @@ async function handleRequest(message) {
|
|
|
62
76
|
throw new Error(`Unsupported worker method: ${method}`);
|
|
63
77
|
}
|
|
64
78
|
const args = Array.isArray(message.args) ? [...message.args] : [];
|
|
65
|
-
if (method === "runTurn") {
|
|
66
|
-
const
|
|
79
|
+
if (method === "runTurn" || method === "runGoal") {
|
|
80
|
+
const firstArg = args[0];
|
|
67
81
|
const options = args[1] && typeof args[1] === "object" ? { ...args[1] } : {};
|
|
68
82
|
options.onProgress = (payload) => {
|
|
69
83
|
send({
|
|
@@ -72,7 +86,7 @@ async function handleRequest(message) {
|
|
|
72
86
|
payload,
|
|
73
87
|
});
|
|
74
88
|
};
|
|
75
|
-
args[0] =
|
|
89
|
+
args[0] = firstArg;
|
|
76
90
|
args[1] = options;
|
|
77
91
|
}
|
|
78
92
|
const result = await session[method](...args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc -p tsconfig.json",
|
|
21
|
-
"test": "
|
|
21
|
+
"test": "npm run test:js && npm run test:manager",
|
|
22
|
+
"test:js": "node --import tsx --test test/*.test.js",
|
|
23
|
+
"test:manager": "node --import tsx --test test/manager/*.test.ts",
|
|
22
24
|
"prepublishOnly": "npm run build"
|
|
23
25
|
},
|
|
24
26
|
"dependencies": {
|
|
@@ -26,11 +28,16 @@
|
|
|
26
28
|
"@github/copilot-sdk": "^0.2.2",
|
|
27
29
|
"@opencode-ai/sdk": "^1.2.25",
|
|
28
30
|
"js-yaml": "^4.1.1",
|
|
31
|
+
"yaml": "^2.5.1",
|
|
29
32
|
"zod": "^4.1.5"
|
|
30
33
|
},
|
|
34
|
+
"optionalDependencies": {
|
|
35
|
+
"@love-moon/chat-web": "^0.4.0"
|
|
36
|
+
},
|
|
31
37
|
"devDependencies": {
|
|
32
38
|
"@types/node": "^22.10.2",
|
|
39
|
+
"tsx": "^4.20.6",
|
|
33
40
|
"typescript": "^5.6.3"
|
|
34
41
|
},
|
|
35
|
-
"gitCommitId": "
|
|
42
|
+
"gitCommitId": "2d1526c"
|
|
36
43
|
}
|