@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.
Files changed (59) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/built-in-backends.d.ts +1 -0
  3. package/dist/built-in-backends.js +6 -0
  4. package/dist/client.d.ts +15 -0
  5. package/dist/client.js +103 -1
  6. package/dist/external-provider-registry.js +4 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +3 -0
  9. package/dist/manager/account.d.ts +6 -0
  10. package/dist/manager/account.js +121 -0
  11. package/dist/manager/auth-parser.d.ts +27 -0
  12. package/dist/manager/auth-parser.js +54 -0
  13. package/dist/manager/config.d.ts +6 -0
  14. package/dist/manager/config.js +32 -0
  15. package/dist/manager/index.d.ts +12 -0
  16. package/dist/manager/index.js +11 -0
  17. package/dist/manager/install.d.ts +9 -0
  18. package/dist/manager/install.js +117 -0
  19. package/dist/manager/manager.d.ts +51 -0
  20. package/dist/manager/manager.js +105 -0
  21. package/dist/manager/network.d.ts +8 -0
  22. package/dist/manager/network.js +46 -0
  23. package/dist/manager/paths.d.ts +6 -0
  24. package/dist/manager/paths.js +16 -0
  25. package/dist/manager/quota/cache.d.ts +9 -0
  26. package/dist/manager/quota/cache.js +33 -0
  27. package/dist/manager/quota/claude.d.ts +19 -0
  28. package/dist/manager/quota/claude.js +193 -0
  29. package/dist/manager/quota/codex.d.ts +27 -0
  30. package/dist/manager/quota/codex.js +182 -0
  31. package/dist/manager/quota/copilot.d.ts +64 -0
  32. package/dist/manager/quota/copilot.js +718 -0
  33. package/dist/manager/quota/external.d.ts +29 -0
  34. package/dist/manager/quota/external.js +176 -0
  35. package/dist/manager/quota/headers.d.ts +5 -0
  36. package/dist/manager/quota/headers.js +29 -0
  37. package/dist/manager/quota/kimi.d.ts +24 -0
  38. package/dist/manager/quota/kimi.js +230 -0
  39. package/dist/manager/types.d.ts +166 -0
  40. package/dist/manager/types.js +1 -0
  41. package/dist/providers/chat-web-session.d.ts +218 -0
  42. package/dist/providers/chat-web-session.js +584 -0
  43. package/dist/providers/claude-agent-sdk-session.d.ts +35 -1
  44. package/dist/providers/claude-agent-sdk-session.js +109 -1
  45. package/dist/providers/codex-app-server-session.d.ts +107 -0
  46. package/dist/providers/codex-app-server-session.js +479 -9
  47. package/dist/providers/copilot-sdk-session.d.ts +9 -1
  48. package/dist/providers/copilot-sdk-session.js +48 -0
  49. package/dist/resume/chat-web.d.ts +20 -0
  50. package/dist/resume/chat-web.js +44 -0
  51. package/dist/resume/index.js +2 -0
  52. package/dist/session-factory.d.ts +3 -1
  53. package/dist/session-factory.js +17 -4
  54. package/dist/shared.d.ts +159 -0
  55. package/dist/shared.js +111 -0
  56. package/dist/transports/codex-app-server-transport.d.ts +1 -0
  57. package/dist/transports/codex-app-server-transport.js +45 -1
  58. package/dist/worker.js +19 -5
  59. package/package.json +10 -3
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { CLAUDE_AGENT_SDK_VARIANT as CLAUDE_PROVIDER_VARIANT } from "../built-in-backends.js";
3
- import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
3
+ import { emitLog, getBoundedEnvInt, isGoalStatus, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
4
4
  const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
5
5
  const MIN_TURN_DEADLINE_MS = 30 * 1000;
6
6
  const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
@@ -126,6 +126,13 @@ function sanitizeSummary(value, maxLen = 180) {
126
126
  return sanitizeForLog(value, maxLen);
127
127
  }
128
128
  export class ClaudeAgentSdkSession extends EventEmitter {
129
+ // Capability advertised via getSnapshot().capabilities so worker proxies
130
+ // can short-circuit runGoal without an IPC round trip. Claude exposes
131
+ // native `/goal` slash command, so this is true.
132
+ static capabilities = Object.freeze({ goal: true });
133
+ getCapabilities() {
134
+ return { ...ClaudeAgentSdkSession.capabilities };
135
+ }
129
136
  constructor(backend, options = {}) {
130
137
  super();
131
138
  this.backend = normalizeClaudeBackend(backend);
@@ -207,6 +214,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
207
214
  }
208
215
  : null,
209
216
  currentTurnStatus: this.getCurrentTurnStatus(),
217
+ capabilities: this.getCapabilities(),
210
218
  };
211
219
  }
212
220
  getSessionInfo() {
@@ -869,6 +877,106 @@ export class ClaudeAgentSdkSession extends EventEmitter {
869
877
  }
870
878
  }
871
879
  }
880
+ /**
881
+ * Trigger Claude's native `/goal` slash command. Implemented by sending the
882
+ * prompt `"/goal <objective>"` through {@link runTurn}, which the Claude
883
+ * Agent SDK natively recognizes as a long-running goal request.
884
+ *
885
+ * Callers should detect support via `typeof session.runGoal === "function"`.
886
+ * When a provider does not support goals it should leave this method
887
+ * undefined; callers MUST surface a clear error rather than silently falling
888
+ * back to {@link runTurn}.
889
+ *
890
+ * Implementation note (N8): Claude's `/goal` slash command resolves the
891
+ * entire goal within a single `query()` iteration. {@link runTurn} drains
892
+ * the SDK iterator to completion (`for await (const message of query)`)
893
+ * before returning, so `turnResult.items` is the full, terminal sequence of
894
+ * messages. If the SDK emits multiple `goal_status` items (e.g. an
895
+ * intermediate "active" followed by a terminal "complete"), we select the
896
+ * LAST one rather than the first so the goal's final state is reflected.
897
+ * When no `goal_status` item is present we fall back to "active" and emit a
898
+ * debug log; that fallback is a signal that the contract has drifted.
899
+ *
900
+ * @param {import("../shared.js").GoalRequest} goal
901
+ * @param {object} [options]
902
+ * @returns {Promise<import("../shared.js").GoalResult>}
903
+ */
904
+ async runGoal(goal, options = {}) {
905
+ const objective = typeof goal?.objective === "string" ? goal.objective.trim() : "";
906
+ if (!objective) {
907
+ throw createTurnError("runGoal requires a non-empty objective", {
908
+ reason: "invalid_goal",
909
+ });
910
+ }
911
+ const prompt = `/goal ${objective}`;
912
+ const turnResult = await this.runTurn(prompt, options);
913
+ const items = Array.isArray(turnResult?.items) ? turnResult.items : [];
914
+ // Walk from the end so we settle on the LAST goal_status item the SDK
915
+ // emitted. The single-shot `query()` iteration has been drained by
916
+ // `runTurn`, so this is the terminal status.
917
+ let goalStatusItem = null;
918
+ for (let idx = items.length - 1; idx >= 0; idx -= 1) {
919
+ const item = items[idx];
920
+ if (!item || typeof item !== "object") {
921
+ continue;
922
+ }
923
+ if (item.type === "goal_status" ||
924
+ item.subtype === "goal_status" ||
925
+ (item.goal_status && typeof item.goal_status === "object")) {
926
+ goalStatusItem = item;
927
+ break;
928
+ }
929
+ }
930
+ const parsedStatus = (() => {
931
+ if (!goalStatusItem) {
932
+ return null;
933
+ }
934
+ const candidate = goalStatusItem.goal_status && typeof goalStatusItem.goal_status === "object"
935
+ ? goalStatusItem.goal_status
936
+ : goalStatusItem;
937
+ const status = typeof candidate?.status === "string" ? candidate.status : null;
938
+ const id = typeof candidate?.id === "string" ? candidate.id : undefined;
939
+ const tokenBudget = candidate?.tokenBudget === null || Number.isFinite(Number(candidate?.tokenBudget))
940
+ ? candidate.tokenBudget
941
+ : undefined;
942
+ return { status, id, tokenBudget };
943
+ })();
944
+ if (!goalStatusItem) {
945
+ // Contract drift: the SDK did not surface a goal_status item in the
946
+ // single query iteration. Log so operators notice; downstream we
947
+ // default to "active" so a still-running goal isn't misclassified as
948
+ // terminal.
949
+ this.trace(`runGoal: no goal_status item found in SDK message stream (${items.length} items); defaulting status to "active"`);
950
+ }
951
+ // Validate the SDK-reported status against the known GoalStatus enum
952
+ // before exposing it. An unknown / typo'd status (e.g. "weird") would
953
+ // otherwise leak through and confuse downstream goal-status consumers
954
+ // (the realtime hub uses isTerminalGoalStatus to decide when to stop
955
+ // polling). Default to "active" so a still-running goal is not
956
+ // misclassified as terminal.
957
+ const rawStatus = typeof parsedStatus?.status === "string" ? parsedStatus.status.trim() : "";
958
+ const validatedStatus = isGoalStatus(rawStatus) ? rawStatus : "active";
959
+ const goalState = {
960
+ id: parsedStatus?.id,
961
+ threadId: this.sessionId || undefined,
962
+ objective,
963
+ status: validatedStatus,
964
+ tokenBudget: goal?.tokenBudget !== undefined
965
+ ? goal.tokenBudget
966
+ : parsedStatus?.tokenBudget !== undefined
967
+ ? parsedStatus.tokenBudget
968
+ : null,
969
+ };
970
+ return {
971
+ text: turnResult?.text || "",
972
+ goal: goalState,
973
+ usage: turnResult?.usage || null,
974
+ metadata: {
975
+ ...(turnResult?.metadata && typeof turnResult.metadata === "object" ? turnResult.metadata : {}),
976
+ goalPrompt: prompt,
977
+ },
978
+ };
979
+ }
872
980
  async close() {
873
981
  if (this.closed) {
874
982
  return;
@@ -1,5 +1,15 @@
1
1
  export class CodexAppServerSession extends EventEmitter<[never]> {
2
+ /**
3
+ * Optional feature flags surfaced to callers via {@link getSnapshot}.
4
+ * Treat as read-only.
5
+ * @type {Readonly<import("../shared.js").SessionCapabilities>}
6
+ */
7
+ static capabilities: Readonly<import("../shared.js").SessionCapabilities>;
2
8
  constructor(backend: any, options?: {});
9
+ /**
10
+ * @returns {import("../shared.js").SessionCapabilities}
11
+ */
12
+ getCapabilities(): import("../shared.js").SessionCapabilities;
3
13
  backend: string;
4
14
  options: {};
5
15
  logger: any;
@@ -32,9 +42,36 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
32
42
  activeAssistantMessageText: string;
33
43
  resolve: null;
34
44
  reject: null;
45
+ } | {
46
+ turnId: string;
47
+ fullText: string;
48
+ activeAssistantMessageId: string;
49
+ activeAssistantMessageText: string;
50
+ resolve: () => void;
51
+ reject: (error: any) => void;
52
+ goalMode: boolean;
35
53
  } | null;
36
54
  bootPromise: Promise<void> | null;
37
55
  booted: boolean;
56
+ goalMode: boolean;
57
+ currentGoal: (import("../shared.js").GoalState & {
58
+ raw?: unknown;
59
+ }) | null;
60
+ currentGoalRun: {
61
+ id: undefined;
62
+ threadId: any;
63
+ objective: string;
64
+ status: string;
65
+ tokenBudget: number | null;
66
+ fullText: string;
67
+ lastTurnText: string;
68
+ latestGoal: null;
69
+ completion: Promise<any>;
70
+ pendingDeltasByTurnId: Map<any, any>;
71
+ handleGoalUpdate: (payload: any) => void;
72
+ handleGoalCleared: (previous: any) => void;
73
+ handleTurnFailed: (error: any) => void;
74
+ } | null;
38
75
  transport: CodexAppServerTransport;
39
76
  writeLog(message: any): void;
40
77
  trace(message: any): void;
@@ -56,6 +93,7 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
56
93
  command: string;
57
94
  } | null;
58
95
  currentTurnStatus: any;
96
+ capabilities: import("../shared.js").SessionCapabilities;
59
97
  pid: number | undefined;
60
98
  };
61
99
  getSessionInfo(): any;
@@ -109,6 +147,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
109
147
  activeAssistantMessageText: string;
110
148
  resolve: null;
111
149
  reject: null;
150
+ } | {
151
+ turnId: string;
152
+ fullText: string;
153
+ activeAssistantMessageId: string;
154
+ activeAssistantMessageText: string;
155
+ resolve: () => void;
156
+ reject: (error: any) => void;
157
+ goalMode: boolean;
112
158
  } | null, { messageId }?: {
113
159
  messageId?: string | undefined;
114
160
  }): Promise<boolean>;
@@ -133,6 +179,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
133
179
  activeAssistantMessageText: string;
134
180
  resolve: null;
135
181
  reject: null;
182
+ } | {
183
+ turnId: string;
184
+ fullText: string;
185
+ activeAssistantMessageId: string;
186
+ activeAssistantMessageText: string;
187
+ resolve: () => void;
188
+ reject: (error: any) => void;
189
+ goalMode: boolean;
136
190
  } | null;
137
191
  ensureCurrentTurn(params: any): {
138
192
  turnId: string;
@@ -141,6 +195,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
141
195
  activeAssistantMessageText: string;
142
196
  resolve: null;
143
197
  reject: null;
198
+ } | {
199
+ turnId: string;
200
+ fullText: string;
201
+ activeAssistantMessageId: string;
202
+ activeAssistantMessageText: string;
203
+ resolve: () => void;
204
+ reject: (error: any) => void;
205
+ goalMode: boolean;
144
206
  } | null;
145
207
  queueAssistantDelta(delta: any, { messageId }?: {
146
208
  messageId?: string | undefined;
@@ -173,6 +235,51 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
173
235
  nativeSessionId: string | undefined;
174
236
  };
175
237
  }>;
238
+ /**
239
+ * Normalize a goal payload from a codex `thread/goal/*` notification into
240
+ * the cross-layer {@link GoalState} shape.
241
+ *
242
+ * @param {unknown} payload
243
+ * @returns {(import("../shared.js").GoalState & { raw?: unknown }) | null}
244
+ */
245
+ normalizeGoalPayload(payload: unknown): (import("../shared.js").GoalState & {
246
+ raw?: unknown;
247
+ }) | null;
248
+ /**
249
+ * Trigger Codex's experimental `thread/goal/set` JSON-RPC. Requires the
250
+ * app-server to have been booted with `--enable goals`; dynamic enablement
251
+ * is NOT supported. Pass `{ goalMode: true }` to the session factory so the
252
+ * transport spawns with the right flag.
253
+ *
254
+ * Resolves when the goal reaches a terminal status (`complete`, `blocked`,
255
+ * `usageLimited`, `budgetLimited`) or when a `thread/goal/cleared`
256
+ * notification arrives.
257
+ *
258
+ * @param {import("../shared.js").GoalRequest} goal
259
+ * @param {object} [options]
260
+ * @returns {Promise<import("../shared.js").GoalResult>}
261
+ */
262
+ runGoal(goal: import("../shared.js").GoalRequest, options?: object): Promise<import("../shared.js").GoalResult>;
263
+ /**
264
+ * Fetch the current goal via `thread/goal/get`.
265
+ *
266
+ * @returns {Promise<import("../shared.js").GoalState | null>}
267
+ */
268
+ getGoal(): Promise<import("../shared.js").GoalState | null>;
269
+ /**
270
+ * Clear the current goal via `thread/goal/clear`.
271
+ *
272
+ * If a {@link runGoal} call is in flight when this is invoked externally,
273
+ * the pending `currentGoalRun.completion` is unblocked with a synthetic
274
+ * "cleared" payload so the caller's `await runGoal(...)` resolves promptly
275
+ * instead of waiting for the turn-timeout. The server-initiated
276
+ * `thread/goal/clear` does not emit a `thread/goal/cleared` notification
277
+ * (it's the response to OUR own RPC), so we have to wake the goal-run
278
+ * ourselves here.
279
+ *
280
+ * @returns {Promise<boolean>} true if a goal was cleared, false if there was nothing to clear.
281
+ */
282
+ clearGoal(): Promise<boolean>;
176
283
  close(): Promise<void>;
177
284
  }
178
285
  import { EventEmitter } from "node:events";