@cydm/happy-elves 0.1.0-beta.50 → 0.1.0-beta.51

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 (35) hide show
  1. package/apps/daemon/dist/relay/register.js +1 -0
  2. package/apps/daemon/dist/runtime/external-provider.js +8 -2
  3. package/apps/daemon/dist/session/lifecycle.js +5 -0
  4. package/apps/daemon/dist/session/prompt.js +11 -1
  5. package/apps/daemon/dist/types.d.ts +4 -1
  6. package/apps/daemon/package.json +1 -1
  7. package/apps/relay/dist/connections.d.ts +2 -0
  8. package/apps/relay/dist/controller-handlers.js +32 -1
  9. package/apps/relay/dist/projections.js +33 -0
  10. package/apps/relay/dist/relay-context.js +9 -0
  11. package/npm-shrinkwrap.json +2 -2
  12. package/package.json +1 -1
  13. package/packages/agent-sdk/dist/index.d.ts +7 -1
  14. package/packages/client/dist/client.js +4 -0
  15. package/packages/client/dist/live-types.d.ts +2 -0
  16. package/packages/client/dist/live.js +2 -0
  17. package/packages/client/dist/types.d.ts +8 -1
  18. package/packages/pie-provider/dist/index.js +59 -4
  19. package/packages/provider-protocol/dist/index.d.ts +67 -0
  20. package/packages/provider-protocol/dist/index.js +16 -0
  21. package/packages/runtime/dist/index.d.ts +8 -2
  22. package/packages/runtime-cli/dist/claude-turn.d.ts +2 -0
  23. package/packages/runtime-cli/dist/claude-turn.js +18 -0
  24. package/packages/runtime-cli/dist/codex-app-server.d.ts +8 -3
  25. package/packages/runtime-cli/dist/codex-app-server.js +85 -17
  26. package/packages/runtime-cli/dist/codex-protocol.d.ts +5 -3
  27. package/packages/runtime-cli/dist/codex-protocol.js +24 -5
  28. package/packages/runtime-cli/dist/index.js +46 -2
  29. package/packages/runtime-cli/dist/session-store.d.ts +2 -0
  30. package/packages/runtime-cli/dist/session-store.js +6 -4
  31. package/packages/shared/dist/protocol-schemas.d.ts +28 -0
  32. package/packages/shared/dist/protocol-schemas.js +12 -0
  33. package/packages/shared/dist/protocol-types.d.ts +12 -0
  34. package/packages/shared/dist/protocol.d.ts +5 -1
  35. package/packages/shared/dist/protocol.js +4 -0
@@ -24,6 +24,7 @@ function agentProfileForRegister(agent) {
24
24
  ...(agent.command ? { command: agent.command } : {}),
25
25
  ...(agent.historyModel ? { historyModel: agent.historyModel } : {}),
26
26
  ...(agent.sessionPrimitives ? { sessionPrimitives: runtimeAgentPrimitives(agent.sessionPrimitives) } : {}),
27
+ ...(agent.runtimeOptions ? { runtimeOptions: agent.runtimeOptions } : {}),
27
28
  };
28
29
  }
29
30
  function runtimeAgentPrimitives(primitives) {
@@ -10,7 +10,7 @@ const maxJsonLineChars = 1024 * 1024;
10
10
  const stderrRingChars = 16 * 1024;
11
11
  const historicalEventPageLimit = 200;
12
12
  const historicalEventPageSafetyLimit = 1_000;
13
- const pieMinimumVersion = "1.0.20";
13
+ const pieMinimumVersion = "1.0.21";
14
14
  const pieAutoProbeTimeoutMs = 5_000;
15
15
  export function externalProviderConfigsFromEnv() {
16
16
  const raw = process.env.HAPPY_ELVES_AGENT_PROVIDERS_JSON?.trim();
@@ -74,11 +74,12 @@ function pieCommandSupportsProvider(command) {
74
74
  const listResult = runPieProbe(command, ["sessions", "list", "--json", "--limit", "1"]);
75
75
  if (listResult.status !== 0)
76
76
  return false;
77
- const modelsResult = runPieProbe(command, ["models", "validate"]);
77
+ const modelsResult = runPieProbe(command, ["models", "validate", "--json"]);
78
78
  if (modelsResult.status !== 0)
79
79
  return false;
80
80
  try {
81
81
  JSON.parse(listResult.stdout);
82
+ JSON.parse(modelsResult.stdout);
82
83
  return true;
83
84
  }
84
85
  catch {
@@ -315,6 +316,7 @@ export function createExternalAgentRuntimeProvider(options) {
315
316
  rename: agent.capabilities.sessionPrimitives.rename ?? "unsupported",
316
317
  }
317
318
  : { resume: "unsupported", rewind: "unsupported", fork: "unsupported", rename: "unsupported" },
319
+ runtimeOptions: agent.capabilities?.runtimeOptions,
318
320
  }));
319
321
  const agentsById = new Map();
320
322
  for (const agent of externalAgents)
@@ -338,6 +340,8 @@ export function createExternalAgentRuntimeProvider(options) {
338
340
  ...(input.resumeHandle ? { resumeHandle: sdkHandle(input.resumeHandle) } : {}),
339
341
  cwd: input.cwd,
340
342
  permissionMode: input.permissionMode,
343
+ model: input.model,
344
+ thoughtLevel: input.thoughtLevel,
341
345
  restartableEmptySession: input.restartableEmptySession,
342
346
  }), "ensureSession result handle");
343
347
  return toRuntimeHandle(input.sessionKey, handle, input.cwd);
@@ -429,6 +433,8 @@ export function createExternalAgentRuntimeProvider(options) {
429
433
  attachments: input.attachments,
430
434
  mode: input.mode,
431
435
  permissionMode: input.permissionMode,
436
+ model: input.model,
437
+ thoughtLevel: input.thoughtLevel,
432
438
  requestId: input.requestId,
433
439
  timeoutMs: input.timeoutMs,
434
440
  });
@@ -22,6 +22,8 @@ export async function handleCreateSession(ws, config, command) {
22
22
  mode: "persistent",
23
23
  cwd,
24
24
  permissionMode: metadata?.permissionMode,
25
+ model: metadata?.modelMode,
26
+ thoughtLevel: metadata?.thoughtLevel,
25
27
  });
26
28
  let providerName;
27
29
  const requestedRuntimeName = metadata?.requestedRuntimeName;
@@ -68,6 +70,9 @@ export async function handleCreateSession(ws, config, command) {
68
70
  ...(providerName ? { name: providerName } : {}),
69
71
  encryptedMetadata: await encryptedSessionMetadata(config, command.sessionId, state, {
70
72
  emptyRuntimeSession: true,
73
+ modelMode: metadata?.modelMode,
74
+ thoughtLevel: metadata?.thoughtLevel,
75
+ runtimeOptions: handle.runtimeOptions,
71
76
  ...(providerName ? { providerName, renamedAt: new Date().toISOString() } : {}),
72
77
  readyAt: new Date().toISOString(),
73
78
  }),
@@ -6,7 +6,7 @@ import { activePromptRequests, completedPromptRequests, sessions, sessionTurns }
6
6
  import { attachClaimedTurn, claimSessionTurn, releaseSessionTurn, sendCommandResponse, sendReliable } from "../relay/send.js";
7
7
  import { emitEvent, runtimeEventToPayload } from "./events.js";
8
8
  import { replaceAuthoritativeTranscript, resumeSession, usesAuthoritativeTranscript } from "./lifecycle.js";
9
- import { encryptedSessionMetadata, sessionFallbackFromCommand } from "./metadata.js";
9
+ import { commandSessionMetadata, encryptedSessionMetadata, sessionFallbackFromCommand } from "./metadata.js";
10
10
  export async function handlePrompt(ws, config, command) {
11
11
  if (command.type !== "machine:prompt")
12
12
  return;
@@ -60,6 +60,8 @@ export async function handlePrompt(ws, config, command) {
60
60
  text,
61
61
  mode: "prompt",
62
62
  permissionMode: command.permissionMode,
63
+ model: command.model,
64
+ thoughtLevel: command.thoughtLevel,
63
65
  requestId: command.requestId,
64
66
  timeoutMs: command.timeoutMs,
65
67
  });
@@ -78,6 +80,8 @@ export async function handlePrompt(ws, config, command) {
78
80
  turnId: command.turnId,
79
81
  promptChars: text.length,
80
82
  permissionMode: command.permissionMode ?? "approve-reads",
83
+ model: command.model ?? undefined,
84
+ thoughtLevel: command.thoughtLevel ?? undefined,
81
85
  },
82
86
  });
83
87
  pumpRuntimeEvents(turn, ws, config, command);
@@ -175,9 +179,15 @@ async function refreshedRuntimeMetadata(config, command, session, result) {
175
179
  if (!session.handle.backendSessionId)
176
180
  return undefined;
177
181
  try {
182
+ const previousMetadata = await commandSessionMetadata(config, command.encryptedMetadata);
183
+ const modelMode = command.model !== undefined ? command.model : previousMetadata?.modelMode;
184
+ const thoughtLevel = command.thoughtLevel !== undefined ? command.thoughtLevel : previousMetadata?.thoughtLevel;
178
185
  return await encryptedSessionMetadata(config, command.sessionId, session, {
179
186
  currentHead: result.currentHead,
180
187
  emptyRuntimeSession: result.status === "failed" && !result.currentHead && !result.lastTurnId,
188
+ ...(modelMode !== undefined ? { modelMode } : {}),
189
+ ...(thoughtLevel !== undefined ? { thoughtLevel } : {}),
190
+ ...(previousMetadata?.runtimeOptions !== undefined ? { runtimeOptions: previousMetadata.runtimeOptions } : {}),
181
191
  runtimeMetadataRefreshedAt: new Date().toISOString(),
182
192
  });
183
193
  }
@@ -1,5 +1,5 @@
1
1
  import type { SessionHeadAdvanceBasis } from "../../../packages/shared/dist/index.js";
2
- import type { RuntimeHandle, RuntimeTurn } from "../../../packages/runtime/dist/index.js";
2
+ import type { RuntimeHandle, RuntimeOptions, RuntimeTurn } from "../../../packages/runtime/dist/index.js";
3
3
  export type DirectoryListResult = {
4
4
  machineId: string;
5
5
  path: string;
@@ -57,6 +57,9 @@ export type SessionMetadata = {
57
57
  providerName?: string;
58
58
  requestedRuntimeName?: string;
59
59
  permissionMode?: PermissionMode;
60
+ modelMode?: string | null;
61
+ thoughtLevel?: string | null;
62
+ runtimeOptions?: RuntimeOptions;
60
63
  importedFromRuntimeSessionId?: string;
61
64
  importedSummary?: string;
62
65
  sourceSessionId?: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves-daemon",
3
- "version": "0.1.0-beta.50",
3
+ "version": "0.1.0-beta.51",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -9,6 +9,7 @@ export type PendingCommandContext = {
9
9
  command?: string;
10
10
  cwd?: string;
11
11
  machineId: string;
12
+ model?: string | null;
12
13
  name?: string;
13
14
  promptHash?: string;
14
15
  recipientDeviceId?: string;
@@ -19,6 +20,7 @@ export type PendingCommandContext = {
19
20
  sourceSessionId?: string;
20
21
  targetSessionId?: string;
21
22
  timeoutMs?: number;
23
+ thoughtLevel?: string | null;
22
24
  turnId?: string;
23
25
  };
24
26
  export declare function machineKey(accountId: string, machineId: string): string;
@@ -169,12 +169,16 @@ export function handleControllerMessage(context, connection, message) {
169
169
  return;
170
170
  }
171
171
  rememberAcceptedRun(connection, { machineId: sessionRow.machine_id, sessionId: sessionRow.id, turnId: message.turnId });
172
+ const model = normalizeRuntimeSelectionContext(message.model);
173
+ const thoughtLevel = normalizeRuntimeSelectionContext(message.thoughtLevel);
172
174
  markCommandPending(connection.accountId, sessionRow.machine_id, message.requestId, {
173
175
  action: "session.run",
174
176
  blocksSessionTurn: true,
177
+ model,
175
178
  promptHash: message.promptHash,
176
179
  recipientDeviceId: connection.deviceId ?? undefined,
177
180
  sessionId: sessionRow.id,
181
+ thoughtLevel,
178
182
  turnId: message.turnId,
179
183
  });
180
184
  sendAck(connection, { type: "server:ack", requestId: message.requestId, ok: true, machineId: sessionRow.machine_id, sessionId: sessionRow.id, action: "session.run" });
@@ -198,6 +202,8 @@ export function handleControllerMessage(context, connection, message) {
198
202
  encryptedPrompt: message.encryptedPrompt,
199
203
  encryptedMetadata: sessionRow.encrypted_metadata ? parseJson(sessionRow.encrypted_metadata, undefined) : undefined,
200
204
  permissionMode: message.permissionMode,
205
+ model,
206
+ thoughtLevel,
201
207
  timeoutMs: message.timeoutMs,
202
208
  });
203
209
  return;
@@ -730,7 +736,15 @@ function controllerMessageCommandContext(message, targetMachineId) {
730
736
  case "controller:createSession":
731
737
  return { action: "session.create", agent: message.agent, cwd: message.cwd, machineId: message.machineId, name: message.name };
732
738
  case "controller:prompt":
733
- return { action: "session.run", machineId: targetMachineId, promptHash: message.promptHash, sessionId: message.sessionId, turnId: message.turnId };
739
+ return {
740
+ action: "session.run",
741
+ machineId: targetMachineId,
742
+ model: normalizeRuntimeSelectionContext(message.model),
743
+ promptHash: message.promptHash,
744
+ sessionId: message.sessionId,
745
+ thoughtLevel: normalizeRuntimeSelectionContext(message.thoughtLevel),
746
+ turnId: message.turnId,
747
+ };
734
748
  case "controller:cancel":
735
749
  return { action: "session.cancel", machineId: targetMachineId, sessionId: message.sessionId, turnId: message.turnId };
736
750
  case "controller:resume":
@@ -791,6 +805,8 @@ function pendingCommandMatchesMessage(pending, expected) {
791
805
  return false;
792
806
  if (!matchesDefined(pending.name, expected.name))
793
807
  return false;
808
+ if (expected.action === "session.run" && !matchesRuntimeSelection(pending.model, expected.model))
809
+ return false;
794
810
  if (!matchesDefined(pending.promptHash, expected.promptHash))
795
811
  return false;
796
812
  if (!matchesDefined(pending.runtimeSessionId, expected.runtimeSessionId))
@@ -801,6 +817,8 @@ function pendingCommandMatchesMessage(pending, expected) {
801
817
  return false;
802
818
  if (!matchesDefined(pending.targetSessionId, expected.targetSessionId))
803
819
  return false;
820
+ if (expected.action === "session.run" && !matchesRuntimeSelection(pending.thoughtLevel, expected.thoughtLevel))
821
+ return false;
804
822
  if (!matchesDefined(pending.turnId, expected.turnId))
805
823
  return false;
806
824
  if (!matchesDefined(pending.checkpoint, expected.checkpoint))
@@ -833,6 +851,8 @@ function retainedResponseMatchesCommand(previous, expected) {
833
851
  return false;
834
852
  if (!matchesDefined(responseTurnId(response), expected.turnId))
835
853
  return false;
854
+ if (expected.action === "session.run" && (expected.model !== undefined || expected.thoughtLevel !== undefined))
855
+ return false;
836
856
  if (expected.action === "machine.devExec" && !legacyDevExecResponseMatches(response, expected))
837
857
  return false;
838
858
  return true;
@@ -911,6 +931,17 @@ function responseTurnId(response) {
911
931
  function matchesDefined(actual, expected) {
912
932
  return expected === undefined || actual === expected;
913
933
  }
934
+ function matchesRuntimeSelection(actual, expected) {
935
+ return actual === expected;
936
+ }
937
+ function normalizeRuntimeSelectionContext(value) {
938
+ if (value === undefined)
939
+ return undefined;
940
+ if (value === null)
941
+ return null;
942
+ const normalized = value.trim();
943
+ return normalized.length === 0 || normalized.toLowerCase() === "default" ? null : normalized;
944
+ }
914
945
  function sendRequestIdConflict(connection, send, requestId) {
915
946
  send(connection.socket, {
916
947
  type: "server:error",
@@ -21,6 +21,38 @@ function primitiveSupport(value) {
21
21
  function historyModel(value) {
22
22
  return value === "append-only" || value === "authoritative-linear" ? value : undefined;
23
23
  }
24
+ function runtimeSelectionOption(value) {
25
+ if (!isRecord(value) || typeof value.key !== "string" || value.key.length === 0 || typeof value.label !== "string" || value.label.length === 0) {
26
+ return undefined;
27
+ }
28
+ return {
29
+ key: value.key,
30
+ label: value.label,
31
+ ...(typeof value.description === "string" || value.description === null ? { description: value.description } : {}),
32
+ };
33
+ }
34
+ function runtimeSelectionOptions(value) {
35
+ if (!Array.isArray(value))
36
+ return undefined;
37
+ const options = value.flatMap((item) => {
38
+ const option = runtimeSelectionOption(item);
39
+ return option ? [option] : [];
40
+ });
41
+ return options.length > 0 ? options : undefined;
42
+ }
43
+ function runtimeOptions(value) {
44
+ if (!isRecord(value))
45
+ return undefined;
46
+ const models = runtimeSelectionOptions(value.models);
47
+ const thoughtLevels = runtimeSelectionOptions(value.thoughtLevels);
48
+ const normalized = {
49
+ ...(models ? { models } : {}),
50
+ ...(thoughtLevels ? { thoughtLevels } : {}),
51
+ ...(typeof value.currentModel === "string" && value.currentModel.length > 0 ? { currentModel: value.currentModel } : {}),
52
+ ...(typeof value.currentThoughtLevel === "string" && value.currentThoughtLevel.length > 0 ? { currentThoughtLevel: value.currentThoughtLevel } : {}),
53
+ };
54
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
55
+ }
24
56
  export function normalizeMachineCapabilities(value) {
25
57
  const record = isRecord(value) ? value : {};
26
58
  const agents = Array.isArray(record.agents)
@@ -32,6 +64,7 @@ export function normalizeMachineCapabilities(value) {
32
64
  label: agent.label,
33
65
  ...(typeof agent.command === "string" ? { command: agent.command } : {}),
34
66
  ...(historyModel(agent.historyModel) ? { historyModel: historyModel(agent.historyModel) } : {}),
67
+ ...(runtimeOptions(agent.runtimeOptions) ? { runtimeOptions: runtimeOptions(agent.runtimeOptions) } : {}),
35
68
  ...(isRecord(agent.sessionPrimitives)
36
69
  ? {
37
70
  sessionPrimitives: {
@@ -285,6 +285,7 @@ export function createRelayContext(options) {
285
285
  ...(pending.command !== undefined ? { command: pending.command } : {}),
286
286
  ...(pending.cwd !== undefined ? { cwd: pending.cwd } : {}),
287
287
  machineId: pending.machineId,
288
+ ...(pending.model !== undefined ? { model: pending.model } : {}),
288
289
  ...(pending.name !== undefined ? { name: pending.name } : {}),
289
290
  ...(pending.promptHash !== undefined ? { promptHash: pending.promptHash } : {}),
290
291
  ...(pending.runtimeSessionId !== undefined ? { runtimeSessionId: pending.runtimeSessionId } : {}),
@@ -293,6 +294,7 @@ export function createRelayContext(options) {
293
294
  ...(pending.sourceSessionId !== undefined ? { sourceSessionId: pending.sourceSessionId } : {}),
294
295
  ...(pending.targetSessionId !== undefined ? { targetSessionId: pending.targetSessionId } : {}),
295
296
  ...(pending.timeoutMs !== undefined ? { timeoutMs: pending.timeoutMs } : {}),
297
+ ...(pending.thoughtLevel !== undefined ? { thoughtLevel: pending.thoughtLevel } : {}),
296
298
  ...(pending.turnId !== undefined ? { turnId: pending.turnId } : {}),
297
299
  });
298
300
  }
@@ -319,6 +321,7 @@ export function createRelayContext(options) {
319
321
  ...(typeof parsed.command === "string" ? { command: parsed.command } : {}),
320
322
  ...(typeof parsed.cwd === "string" ? { cwd: parsed.cwd } : {}),
321
323
  machineId: parsed.machineId,
324
+ ...(runtimeSelectionContext(parsed.model) !== undefined ? { model: runtimeSelectionContext(parsed.model) } : {}),
322
325
  ...(typeof parsed.name === "string" ? { name: parsed.name } : {}),
323
326
  ...(typeof parsed.promptHash === "string" ? { promptHash: parsed.promptHash } : {}),
324
327
  requestId: "",
@@ -328,6 +331,7 @@ export function createRelayContext(options) {
328
331
  ...(typeof parsed.sourceSessionId === "string" ? { sourceSessionId: parsed.sourceSessionId } : {}),
329
332
  ...(typeof parsed.targetSessionId === "string" ? { targetSessionId: parsed.targetSessionId } : {}),
330
333
  ...(typeof parsed.timeoutMs === "number" ? { timeoutMs: parsed.timeoutMs } : {}),
334
+ ...(runtimeSelectionContext(parsed.thoughtLevel) !== undefined ? { thoughtLevel: runtimeSelectionContext(parsed.thoughtLevel) } : {}),
331
335
  ...(typeof parsed.turnId === "string" ? { turnId: parsed.turnId } : {}),
332
336
  };
333
337
  }
@@ -335,6 +339,11 @@ export function createRelayContext(options) {
335
339
  return undefined;
336
340
  }
337
341
  }
342
+ function runtimeSelectionContext(value) {
343
+ if (value === null)
344
+ return null;
345
+ return typeof value === "string" ? value : undefined;
346
+ }
338
347
  function rememberDirectCommandResponse(accountId, machineId, requestId, response) {
339
348
  const pending = pendingWithMachine(accountId, machineId, requestId);
340
349
  const recipientDeviceId = pending?.recipientDeviceId;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.50",
3
+ "version": "0.1.0-beta.51",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@cydm/happy-elves",
9
- "version": "0.1.0-beta.50",
9
+ "version": "0.1.0-beta.51",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@fastify/cors": "11.2.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/happy-elves",
3
- "version": "0.1.0-beta.50",
3
+ "version": "0.1.0-beta.51",
4
4
  "description": "Remote controller for local coding agents with hosted or self-hosted relay support.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  import type { Readable, Writable } from "node:stream";
2
- import { type ProviderAgentCapability, type ProviderAgentProfile, type ProviderDiagnostics, type ProviderErrorCode, type ProviderHistoricalEvent as HistoricalEvent, type ProviderHistoricalEventPage as HistoricalEventPage, type ProviderHistoricalSession as HistoricalSession, type ProviderHistoricalSessionPage as HistoricalSessionPage, type ProviderInfo, type ProviderMethod, type ProviderPermissionMode, type ProviderPrimitiveSupport, type ProviderPromptMode, type ProviderRequest, type ProviderResponse, type ProviderRuntimeAttachment, type ProviderRuntimeEvent, type ProviderRuntimeHandle, type ProviderRuntimeTurnResult, type ProviderSessionMode, type ProviderStreamMessage } from "../../provider-protocol/dist/index.js";
2
+ import { type ProviderAgentCapability, type ProviderAgentProfile, type ProviderDiagnostics, type ProviderErrorCode, type ProviderHistoricalEvent as HistoricalEvent, type ProviderHistoricalEventPage as HistoricalEventPage, type ProviderHistoricalSession as HistoricalSession, type ProviderHistoricalSessionPage as HistoricalSessionPage, type ProviderInfo, type ProviderMethod, type ProviderPermissionMode, type ProviderPrimitiveSupport, type ProviderPromptMode, type ProviderRequest, type ProviderResponse, type ProviderRuntimeAttachment, type ProviderRuntimeEvent, type ProviderRuntimeOptions, type ProviderRuntimeSelectionOption, type ProviderRuntimeHandle, type ProviderRuntimeTurnResult, type ProviderSessionMode, type ProviderStreamMessage } from "../../provider-protocol/dist/index.js";
3
3
  export type PrimitiveSupport = ProviderPrimitiveSupport;
4
4
  export type PermissionMode = ProviderPermissionMode;
5
5
  export type PromptMode = ProviderPromptMode;
@@ -7,6 +7,8 @@ export type SessionMode = ProviderSessionMode;
7
7
  export type AgentCapability = ProviderAgentCapability;
8
8
  export type AgentProfile = ProviderAgentProfile;
9
9
  export type { ProviderInfo };
10
+ export type RuntimeOptions = ProviderRuntimeOptions;
11
+ export type RuntimeSelectionOption = ProviderRuntimeSelectionOption;
10
12
  export type RuntimeHandle = ProviderRuntimeHandle;
11
13
  export type RuntimeAttachment = ProviderRuntimeAttachment;
12
14
  export type RuntimeEvent = ProviderRuntimeEvent;
@@ -43,6 +45,8 @@ export interface HappyElvesAgentProvider {
43
45
  resumeHandle?: RuntimeHandle;
44
46
  cwd?: string;
45
47
  permissionMode?: PermissionMode;
48
+ model?: string | null;
49
+ thoughtLevel?: string | null;
46
50
  restartableEmptySession?: boolean;
47
51
  }): Promise<RuntimeHandle>;
48
52
  startTurn(input: {
@@ -51,6 +55,8 @@ export interface HappyElvesAgentProvider {
51
55
  attachments?: RuntimeAttachment[];
52
56
  mode: PromptMode;
53
57
  permissionMode?: PermissionMode;
58
+ model?: string | null;
59
+ thoughtLevel?: string | null;
54
60
  requestId: string;
55
61
  timeoutMs?: number;
56
62
  signal?: AbortSignal;
@@ -149,6 +149,8 @@ export class ControllerClient {
149
149
  createdFrom: input.createdFrom ?? "cli",
150
150
  createRequestId: requestId,
151
151
  permissionMode: input.permissionMode,
152
+ modelMode: input.model,
153
+ thoughtLevel: input.thoughtLevel,
152
154
  }),
153
155
  });
154
156
  if (result.sessionId) {
@@ -416,6 +418,8 @@ export class ControllerClient {
416
418
  }),
417
419
  ...(options.promptHash === undefined ? {} : { promptHash: requireNonEmpty(options.promptHash, "promptHash") }),
418
420
  permissionMode: options.permissionMode ?? "approve-reads",
421
+ model: options.model,
422
+ thoughtLevel: options.thoughtLevel,
419
423
  timeoutMs: options.runtimeTimeoutMs,
420
424
  }, wait, timeoutMs);
421
425
  if (wait) {
@@ -74,6 +74,8 @@ export type SendPromptInput = {
74
74
  sessionId: string;
75
75
  text: string;
76
76
  permissionMode?: PermissionMode;
77
+ model?: string | null;
78
+ thoughtLevel?: string | null;
77
79
  timeoutMs?: number;
78
80
  };
79
81
  export type SendPromptResult = {
@@ -169,6 +169,8 @@ export class ControllerLiveProjectionImpl {
169
169
  turnId,
170
170
  encryptedPrompt,
171
171
  permissionMode: input.permissionMode ?? "approve-reads",
172
+ model: input.model,
173
+ thoughtLevel: input.thoughtLevel,
172
174
  timeoutMs: input.timeoutMs,
173
175
  }));
174
176
  });
@@ -1,4 +1,4 @@
1
- import type { DirectoryListing, EncryptedSessionEvent, FilePreviewMode, FilePreviewResult, HistoricalSessionSnapshot, MachineSnapshot, MachineDevExecResult, MachineDiagnostics, ServerMessage, SessionEventPayload, SessionSnapshot } from "../../shared/dist/index.js";
1
+ import type { DirectoryListing, EncryptedSessionEvent, FilePreviewMode, FilePreviewResult, HistoricalSessionSnapshot, MachineSnapshot, MachineDevExecResult, MachineDiagnostics, RuntimeOptions, ServerMessage, SessionEventPayload, SessionSnapshot } from "../../shared/dist/index.js";
2
2
  export type ControllerClientConfig = {
3
3
  relayUrl: string;
4
4
  controllerToken: string;
@@ -123,6 +123,9 @@ export type SessionMetadata = {
123
123
  cwd?: string;
124
124
  agent?: string;
125
125
  permissionMode?: PermissionMode;
126
+ modelMode?: string | null;
127
+ thoughtLevel?: string | null;
128
+ runtimeOptions?: RuntimeOptions;
126
129
  [key: string]: unknown;
127
130
  };
128
131
  export type SessionFilter = {
@@ -139,10 +142,14 @@ export type CreateSessionInput = {
139
142
  runtimeName?: string;
140
143
  createdFrom?: string;
141
144
  permissionMode?: PermissionMode;
145
+ model?: string | null;
146
+ thoughtLevel?: string | null;
142
147
  };
143
148
  export type RunOptions = {
144
149
  wait?: boolean;
145
150
  permissionMode?: PermissionMode;
151
+ model?: string | null;
152
+ thoughtLevel?: string | null;
146
153
  requestId?: string;
147
154
  turnId?: string;
148
155
  promptHash?: string;
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn, spawnSync } from "node:child_process";
3
3
  import fs from "node:fs";
4
+ import os from "node:os";
4
5
  import path from "node:path";
5
6
  import readline from "node:readline";
6
7
  import { ProviderError, serveHappyElvesProvider, } from "../../agent-sdk/dist/index.js";
7
8
  const providerId = "pie";
8
9
  const pieCommand = process.env.HAPPY_ELVES_PIE_COMMAND || "pie";
9
10
  const pieConfigDir = process.env.HAPPY_ELVES_PIE_CONFIG_DIR;
10
- const minimumPieVersion = "1.0.20";
11
+ const minimumPieVersion = "1.0.21";
11
12
  const stderrRingChars = 16 * 1024;
12
13
  const defaultTurnTimeoutMs = 10 * 60 * 1000;
13
14
  const pieCommandTimeoutMs = 60_000;
@@ -18,6 +19,55 @@ let stderrRing = "";
18
19
  let lastError;
19
20
  let pieVersion = process.env.HAPPY_ELVES_PIE_VERSION || readPieVersionSync();
20
21
  let pieSupportCheck;
22
+ function normalizeRuntimeSelection(value) {
23
+ if (value === null)
24
+ return null;
25
+ const normalized = value.trim();
26
+ return normalized && normalized !== "default" ? normalized : null;
27
+ }
28
+ function pieRuntimeOptions() {
29
+ const models = readPieModelOptions();
30
+ return {
31
+ ...(models.length > 0 ? { models: [{ key: "default", label: "Default" }, ...models] } : {}),
32
+ thoughtLevels: [
33
+ { key: "default", label: "Default" },
34
+ { key: "off", label: "Off" },
35
+ { key: "minimal", label: "Minimal" },
36
+ { key: "low", label: "Low" },
37
+ { key: "medium", label: "Medium" },
38
+ { key: "high", label: "High" },
39
+ { key: "xhigh", label: "X High" },
40
+ ],
41
+ };
42
+ }
43
+ function readPieModelOptions() {
44
+ const modelsPath = path.join(pieConfigDir || path.join(os.homedir(), ".pie"), "models.json");
45
+ let parsed;
46
+ try {
47
+ parsed = JSON.parse(fs.readFileSync(modelsPath, "utf8"));
48
+ }
49
+ catch {
50
+ return [];
51
+ }
52
+ if (!isRecord(parsed) || !isRecord(parsed.profiles))
53
+ return [];
54
+ const options = [];
55
+ for (const [profileId, profile] of Object.entries(parsed.profiles)) {
56
+ if (!isRecord(profile) || !Array.isArray(profile.models))
57
+ continue;
58
+ for (const model of profile.models) {
59
+ if (!isRecord(model) || typeof model.id !== "string" || model.id.length === 0)
60
+ continue;
61
+ const label = typeof model.name === "string" && model.name.trim() ? model.name.trim() : `${profileId}/${model.id}`;
62
+ options.push({
63
+ key: `${profileId}/${model.id}`,
64
+ label,
65
+ ...(model.reasoning === true ? { description: "Supports thinking levels" } : {}),
66
+ });
67
+ }
68
+ }
69
+ return options;
70
+ }
21
71
  const provider = {
22
72
  getProviderInfo() {
23
73
  return { id: providerId, label: "Pie", version: pieVersion || "unknown" };
@@ -32,6 +82,7 @@ const provider = {
32
82
  sessionPrimitives: { resume: "authoritative", rewind: "authoritative", fork: "authoritative", rename: "provider" },
33
83
  permissionModes: ["approve-reads", "approve-all"],
34
84
  history: true,
85
+ runtimeOptions: pieRuntimeOptions(),
35
86
  },
36
87
  }];
37
88
  },
@@ -149,7 +200,11 @@ const provider = {
149
200
  turnId,
150
201
  prompt: input.text,
151
202
  cwd: input.handle.cwd,
152
- metadata: { permissionMode },
203
+ metadata: {
204
+ permissionMode,
205
+ ...(input.model !== undefined ? { model: normalizeRuntimeSelection(input.model) } : {}),
206
+ ...(input.thoughtLevel !== undefined ? { thinkingLevel: normalizeRuntimeSelection(input.thoughtLevel) } : {}),
207
+ },
153
208
  });
154
209
  };
155
210
  void run().catch((error) => {
@@ -529,14 +584,14 @@ async function checkPieSessionsSupport() {
529
584
  throw new ProviderError("PROVIDER_UNAVAILABLE", `Pie CLI version ${pieVersion} is too old; expected >= ${minimumPieVersion}`, { detailCode: "PIE_VERSION_TOO_OLD", retryable: false });
530
585
  }
531
586
  await runPieJson(["sessions", "list", "--json", "--limit", "1"]);
532
- await runPieText(["models", "validate"]);
587
+ await runPieJson(["models", "validate", "--json"]);
533
588
  }
534
589
  catch (error) {
535
590
  if (error instanceof ProviderError && (error.detailCode === "PIE_VERSION_TOO_OLD" || error.retryable === false)) {
536
591
  lastError = error.message;
537
592
  throw error;
538
593
  }
539
- const message = `Pie command is unavailable or not fully configured; expected \`pie sessions list --json\` and \`pie models validate\` to succeed. ${normalizeError(error).message}`;
594
+ const message = `Pie command is unavailable or not fully configured; expected \`pie sessions list --json\` and \`pie models validate --json\` to succeed. ${normalizeError(error).message}`;
540
595
  lastError = message;
541
596
  throw new ProviderError("PROVIDER_UNAVAILABLE", message, { detailCode: "PIE_SESSIONS_UNAVAILABLE", retryable: true });
542
597
  }