@jmanuelcorral/openteam 0.24.1 → 0.26.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/README.es.md +42 -10
  2. package/README.md +37 -8
  3. package/dist/certificates/graph-release-certificate.json +2 -2
  4. package/dist/certificates/graph-shadow-certificate.json +2 -2
  5. package/dist/cli/setupAdapters.d.ts.map +1 -1
  6. package/dist/cli.js +1200 -384
  7. package/dist/commands/agents.d.ts.map +1 -1
  8. package/dist/commands/dispatch.d.ts.map +1 -1
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/setup.d.ts +20 -15
  11. package/dist/commands/setup.d.ts.map +1 -1
  12. package/dist/config/schema.d.ts +101 -7
  13. package/dist/config/schema.d.ts.map +1 -1
  14. package/dist/console/assets.d.ts.map +1 -1
  15. package/dist/console/conversation.d.ts +5 -1
  16. package/dist/console/conversation.d.ts.map +1 -1
  17. package/dist/console/credentials.d.ts +4 -0
  18. package/dist/console/credentials.d.ts.map +1 -0
  19. package/dist/console/inspector.d.ts +1 -1
  20. package/dist/console/inspector.d.ts.map +1 -1
  21. package/dist/console/refresh.d.ts +1 -1
  22. package/dist/console/refresh.d.ts.map +1 -1
  23. package/dist/console/render.d.ts.map +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +657 -128
  26. package/dist/local/lemonadeResidency.d.ts +2 -14
  27. package/dist/local/lemonadeResidency.d.ts.map +1 -1
  28. package/dist/local/llamaSwap.d.ts +5 -0
  29. package/dist/local/llamaSwap.d.ts.map +1 -0
  30. package/dist/local/llamaSwapRequestGuard.d.ts +11 -0
  31. package/dist/local/llamaSwapRequestGuard.d.ts.map +1 -0
  32. package/dist/local/llamaSwapResidency.d.ts +9 -0
  33. package/dist/local/llamaSwapResidency.d.ts.map +1 -0
  34. package/dist/local/modelResidency.d.ts +1 -0
  35. package/dist/local/modelResidency.d.ts.map +1 -1
  36. package/dist/local/registry.d.ts.map +1 -1
  37. package/dist/local/residency.d.ts +16 -0
  38. package/dist/local/residency.d.ts.map +1 -0
  39. package/dist/local/types.d.ts +10 -2
  40. package/dist/local/types.d.ts.map +1 -1
  41. package/dist/messages/consoleDetail.d.ts +8 -5
  42. package/dist/messages/consoleDetail.d.ts.map +1 -1
  43. package/dist/messages/executionSetup.d.ts +22 -3
  44. package/dist/messages/executionSetup.d.ts.map +1 -1
  45. package/dist/messages/memoryRuntime.d.ts +6 -0
  46. package/dist/messages/memoryRuntime.d.ts.map +1 -0
  47. package/dist/messages/memoryTool.d.ts +1 -1
  48. package/dist/messages/memoryTool.d.ts.map +1 -1
  49. package/dist/messages/modelResidency.d.ts +20 -2
  50. package/dist/messages/modelResidency.d.ts.map +1 -1
  51. package/dist/plugin/availability.d.ts.map +1 -1
  52. package/dist/plugin/memoryTool.d.ts.map +1 -1
  53. package/dist/plugin/modelResidency.d.ts.map +1 -1
  54. package/dist/storage/index/memoryRuntime.d.ts.map +1 -1
  55. package/dist/web/conversationRoute.d.ts +2 -2
  56. package/dist/web/conversationRoute.d.ts.map +1 -1
  57. package/dist/web/server.d.ts +1 -1
  58. package/dist/web/server.d.ts.map +1 -1
  59. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -786,6 +786,9 @@ function migrateConfig(raw) {
786
786
  var consoleDetailMessages = {
787
787
  follow: "Follow new activity",
788
788
  metadataOnly: "Lifecycle and telemetry stay metadata-only.",
789
+ logsDescription: "Current recorded conversations load automatically. Lifecycle and telemetry remain metadata-only; credentials are masked.",
790
+ credentialMasked: "[credential masked]",
791
+ offscreen: "Messages load automatically when this conversation is visible.",
789
792
  filter: "Filter sessions",
790
793
  incoming: "New activity available — show updates",
791
794
  refreshError: "Live updates unavailable; retrying.",
@@ -806,7 +809,7 @@ var consoleDetailMessages = {
806
809
  partialMetadata: "Some current session titles are unavailable or outside the bounded batch; fallback labels remain.",
807
810
  rootCurrent: "Current whole orchestrator session; may contain multiple batches. Not a historical or batch-only transcript.",
808
811
  recovered: "Read-only association verified from current opencode history; not an immutable lifecycle event.",
809
- rootUnlinked: "Orchestrator session not recorded. Reveal to try a bounded, read-only relationship search.",
812
+ rootUnlinked: "Orchestrator session not recorded. A bounded, read-only relationship search is attempted when visible.",
810
813
  rootUnavailable: "Orchestrator session unavailable: no unique, verifiable relationship was found in local history.",
811
814
  rootAmbiguous: "Orchestrator session unavailable: conflicting or copied relationships are ambiguous.",
812
815
  searchIncomplete: "Orchestrator association search incomplete or exceeded its bounded budget. No session was guessed.",
@@ -819,9 +822,9 @@ var consoleDetailMessages = {
819
822
  arguments: "Arguments",
820
823
  result: "Result",
821
824
  loading: "Loading local conversation…",
822
- current: "Present-day session, not a timeline reconstruction. May include later messages and tools. Refresh explicitly for updates.",
823
- historical: "Historical cursor: conversation content is hidden to avoid showing future mutable text. Return to live before revealing this operation.",
824
- disabled: "Content is disabled. Set console.sessionHistory.contentEnabled to true in .opencode/openteam.json and restart the Console. Then reveal content here.",
825
+ current: "Present-day whole session, not a timeline reconstruction or per-attempt transcript. Visible messages update automatically while live.",
826
+ historical: "Historical cursor: conversation content is hidden to avoid showing future mutable text. Return to live to read current messages.",
827
+ disabled: "Content is disabled by console.sessionHistory.contentEnabled: false. Lifecycle and telemetry remain metadata-only.",
825
828
  unavailable: "Conversation unavailable. Start an operator-owned local opencode serve against the same workspace and user history, configure console.sessionHistory.endpoint, then retry. No inference was requested.",
826
829
  empty: "No readable user messages, assistant output or tool calls remain in this session.",
827
830
  invalid: "This attempt has no recorded session in the selected run, or its history is incomplete.",
@@ -831,7 +834,7 @@ var consoleDetailMessages = {
831
834
  invalidEndpoint: "Expected a loopback HTTP origin without credentials, path, query or fragment.",
832
835
  readFailed: "Local session history could not be read.",
833
836
  oversized: "Conversation exceeds the local size limit (4 MiB). No content was loaded.",
834
- truncated: "Conversation display is truncated by local safety limits; omitted content is not empty history.",
837
+ truncated: "Conversation display is truncated by local safety limits. The newest bounded message window is shown; older messages or oversized fields may be omitted, not empty history.",
835
838
  truncatedMarker: `
836
839
  [truncated]`,
837
840
  terminalGap: "Live collection was interrupted; events during the gap are unavailable. This is not complete session history.",
@@ -908,7 +911,7 @@ var ConsoleConfigSchema = z3.object({
908
911
  openBrowser: z3.boolean().default(false),
909
912
  remoteStorage: z3.boolean().default(false),
910
913
  sessionHistory: z3.object({
911
- contentEnabled: z3.boolean().default(false),
914
+ contentEnabled: z3.boolean().default(true),
912
915
  endpoint: z3.string().refine(isLocalHistoryEndpoint, consoleDetailMessages.invalidEndpoint).optional()
913
916
  }).strict().optional(),
914
917
  terminal: z3.object({
@@ -1046,7 +1049,8 @@ var LocalRuntimeKindSchema = z3.enum([
1046
1049
  "ollama",
1047
1050
  "lmstudio",
1048
1051
  "foundry-local",
1049
- "lemonade"
1052
+ "lemonade",
1053
+ "llama-swap"
1050
1054
  ]);
1051
1055
  var LocalRuntimeSchema = z3.object({
1052
1056
  id: z3.string().min(1),
@@ -1066,15 +1070,25 @@ var LocalRuntimeSchema = z3.object({
1066
1070
  ctx.addIssue({
1067
1071
  code: z3.ZodIssueCode.custom,
1068
1072
  path: ["kind"],
1069
- message: `local runtime "${runtime.id}" has no resolvable kind: add "kind" ` + "(one of ollama, lmstudio, foundry-local, lemonade), since the id is " + "not itself a runtime kind."
1073
+ message: `local runtime "${runtime.id}" has no resolvable kind: add "kind" ` + "(one of ollama, lmstudio, foundry-local, lemonade, llama-swap), " + "since the id is not itself a runtime kind."
1070
1074
  });
1071
1075
  }
1076
+ }).transform((runtime) => {
1077
+ const resolvedKind = runtime.kind ?? runtime.id;
1078
+ if (resolvedKind !== "llama-swap" || runtime.maxConcurrency !== undefined) {
1079
+ return runtime;
1080
+ }
1081
+ return { ...runtime, maxConcurrency: 1 };
1072
1082
  });
1073
1083
  function localRuntimeKind(runtime) {
1074
1084
  return runtime.kind ?? runtime.id;
1075
1085
  }
1076
1086
  function effectiveMaxConcurrency(runtime) {
1077
- return runtime.maxConcurrency ?? DEFAULT_LOCAL_MAX_CONCURRENCY;
1087
+ if (runtime.maxConcurrency !== undefined) {
1088
+ return runtime.maxConcurrency;
1089
+ }
1090
+ const kind = runtime.kind ?? (runtime.id !== undefined && LocalRuntimeKindSchema.safeParse(runtime.id).success ? runtime.id : undefined);
1091
+ return kind === "llama-swap" ? 1 : DEFAULT_LOCAL_MAX_CONCURRENCY;
1078
1092
  }
1079
1093
  var MemoryScopeSchema = z3.enum(["project", "user"]);
1080
1094
  var MemoryExtractionModeSchema = z3.enum([
@@ -5743,7 +5757,7 @@ import { z as z15 } from "zod";
5743
5757
  // package.json
5744
5758
  var package_default = {
5745
5759
  name: "@jmanuelcorral/openteam",
5746
- version: "0.24.1",
5760
+ version: "0.26.0",
5747
5761
  packageManager: "bun@1.3.14",
5748
5762
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
5749
5763
  license: "MIT",
@@ -6100,6 +6114,23 @@ var executionSetupMessages = {
6100
6114
  `),
6101
6115
  localOnlyTitle: "Local execution",
6102
6116
  localRequiredError: "local execution requires an enabled local runtime",
6117
+ llamaSwap: {
6118
+ label: "llama-swap",
6119
+ noLoadedModels: "llama-swap has no canonical models verified as ready with no active routing profile. Keep the intended model ready through the operator's existing runtime controls, then rerun setup; manual IDs cannot bypass admission.",
6120
+ modelNotLoaded: (model) => `llama-swap model "${model}" is not positively verified as canonical and ready without an active routing profile. No substitute was selected.`,
6121
+ probeRequired: "llama-swap setup requires a fresh read-only residency probe before writing configuration. Manual model IDs cannot bypass this requirement.",
6122
+ probeFailed: "llama-swap readiness could not be verified. Check the configured /v1/models catalog and origin /running and /api/profiles endpoints; authenticated runtimes are not supported.",
6123
+ endpointMismatch: "llama-swap endpoint/provider mapping differs from the selected runtime. Reconcile the configured origin or /v1 URL and provider; no configuration was written.",
6124
+ modelIdentityUnknown: (model) => `llama-swap model "${model}" has no usable registered physical identity. Fix its model declaration; setup will not guess an alias.`,
6125
+ declaredLimits: "llama-swap context and output limits are operator-declared metadata, not measured per-slot capacities. Missing output metadata uses openteam's client policy default, not a guessed model cap. Unknown context remains 0 and disables automatic compaction. Tool support remains unknown without evidence.",
6126
+ declaredLimitsTitle: "Declared llama-swap limits",
6127
+ outputReduction: (model, previous, ceiling) => `${model}: reducing limit.output from ${previous} to ${ceiling} to respect the declared runtime ceiling. Smaller operator limits are preserved.`,
6128
+ outputReductionTitle: "Declared output ceiling",
6129
+ outputChanged: (model, ceiling) => `${model}: fresh runtime metadata declares an output ceiling of ${ceiling} below the prepared configuration. Rerun setup to review the updated limit; no configuration was written.`
6130
+ },
6131
+ residencyV2Title: "Resident runtime transport compatibility",
6132
+ residencyV2Restriction: (providers) => `Project policies disable opencode's separate native/core V2 transport for resident-only providers: ${providers.join(", ")}. Guarded V1 provider transport remains supported; this does not ban the public SDK v2 client library. Higher-precedence policies can override project restrictions. Readiness preflight is not an atomic no-autoload guarantee.`,
6133
+ retainedResidencyProviderUnprotected: (providerID) => `Cannot safely retain resident-only provider "${providerID}": the effective configuration must retain its matching runtime, disabled when deselected. Reconcile the runtime/provider configuration; no configuration was written.`,
6103
6134
  lemonadeDetectedHint: (loaded, advertised) => `detected - ${loaded} confirmed loaded of ${advertised} advertised text model(s)`,
6104
6135
  lemonadeNoLoadedModels: "Lemonade has no text models verified as loaded and ready. Keep the intended model active in Lemonade and rerun setup; inactive or unknown models cannot be selected or entered manually.",
6105
6136
  lemonadeModelNotLoaded: (model) => `Lemonade model "${model}" is not positively verified as loaded and ready. Keep that exact text model active and rerun setup; no substitute was selected and no implicit loading is allowed.`,
@@ -6114,11 +6145,11 @@ var executionSetupMessages = {
6114
6145
  lemonadeV2InvalidPolicies: "Cannot safely preserve experimental.policies: experimental must be an object and policies must be an array of provider.use statements with a string resource and allow/deny effect. Fix the project configuration before rerunning setup.",
6115
6146
  lemonadeV2Title: "Lemonade transport compatibility",
6116
6147
  lemonadeV2Restriction: (providers) => `Project policies disable native V2 Lemonade access for: ${providers.join(", ")}. Ordinary guarded V1 configuration remains supported; V2 is not an openteam-hook execution path. Higher-precedence or global policies can override this project restriction; it is not a server-atomic no-autoload guarantee.`,
6117
- lemonadeInventory: (instanceID, providerID, models) => {
6148
+ lemonadeInventory: (instanceID, providerID, models, runtime = "Lemonade") => {
6118
6149
  const inventory = models.map((model) => `${model.modelID} (${model.loaded === true ? "loaded" : model.loaded === false ? "inactive" : "residency unknown"})`).join(", ");
6119
- return `Lemonade inventory ${instanceID} (provider ${providerID}): ${inventory || "no advertised text models"}. Only confirmed loaded text models count as ready.`;
6150
+ return `${runtime} inventory ${instanceID} (provider ${providerID}): ${inventory || "no advertised text models"}. Only confirmed loaded text models count as ready.`;
6120
6151
  },
6121
- lemonadeAgentResidency: (agent, model, state) => `Lemonade readiness: ${agent} (${model}): ${state === "not-loaded" ? "inactive (not ready)" : "residency unknown"}; advertised inventory is not loaded readiness. Keep this exact model active and verify residency before use; requests without verified readiness are blocked.`,
6152
+ lemonadeAgentResidency: (agent, model, state, runtime = "Lemonade") => `${runtime} readiness: ${agent} (${model}): ${state === "not-loaded" ? "inactive (not ready)" : "residency unknown"}; advertised inventory is not loaded readiness. Keep this exact model active and verify residency before use; requests without verified readiness are blocked.`,
6122
6153
  mixedPrimaryQuestion: "Primary coordinator model",
6123
6154
  mixedPrimaryLocal: (model) => `Use local thinking model ${model}`,
6124
6155
  mixedPrimaryFrontier: "Choose a frontier model",
@@ -6222,6 +6253,8 @@ var executionPolicyMessages = {
6222
6253
  primaryLocalMismatchRemedy: (sourceFile) => ` remedy: re-run \`openteam setup\` to regenerate ${sourceFile} and opencode.json from the same primary policy, then re-run \`openteam doctor\`.`
6223
6254
  };
6224
6255
  var doctorMessages = {
6256
+ defaultRuntimeSlots: (slots) => ` · ${slots} slot(s) (default)`,
6257
+ llamaSwapConcurrency: " llama-swap does not report per-model slots; openteam uses the configured runtime-wide concurrency limit. No per-model capacity was inferred.",
6225
6258
  localModelLimits: {
6226
6259
  section: " local model limits:",
6227
6260
  healthy: " ✓ enabled local provider models declare valid output and context limits in opencode.json.",
@@ -6238,7 +6271,7 @@ var doctorMessages = {
6238
6271
  invalidInput: "invalid limit.input (expected a positive integer token count)",
6239
6272
  outputExceedsContext: "limit.output is greater than or equal to limit.context, so the configured context leaves no usable input window",
6240
6273
  outputExceedsInput: "limit.input is less than or equal to the reserved output budget, so compaction would have no usable input threshold",
6241
- outputRemedy: " remedy: re-run `openteam setup` to write openteam's 8 192-token local output default, then re-run `openteam doctor`.",
6274
+ outputRemedy: " remedy: re-run `openteam setup` to write openteam's local output policy (at most 8 192 tokens, narrowed by declared runtime ceilings and smaller context/input budgets), then re-run `openteam doctor`.",
6242
6275
  unconfiguredLimitRemedy: " remedy: the optional limit block may remain absent; to configure explicit limits, manually add a complete limit object in opencode.json with positive output and non-negative context (0 means unknown), because setup fills an absent limit block only for models the runtime currently discovers. Then re-run `openteam doctor`.",
6243
6276
  detectedContextRemedy: " remedy: re-run `openteam setup` while the runtime is reachable to copy the detected usable context budget, then re-run `openteam doctor`.",
6244
6277
  unknownContextRemedy: " remedy: re-run `openteam setup` to write the required context:0 sentinel; automatic compaction remains disabled until a positive context is configured.",
@@ -9950,14 +9983,12 @@ function ensureFoundryV1BaseURL(endpoint) {
9950
9983
  }
9951
9984
  }
9952
9985
 
9953
- // src/local/lemonadeResidency.ts
9954
- import { z as z19 } from "zod";
9955
-
9956
9986
  // src/messages/modelResidency.ts
9957
9987
  var reasons = {
9958
9988
  unknown: "fresh residency metadata is unavailable, malformed, or ambiguous",
9959
9989
  inactive: "the requested model is not verified as resident and serving",
9960
9990
  identity: "the requested model identity can be rewritten or is ambiguous",
9991
+ "authentication-unsupported": "authentication is required but unsupported by this integration",
9961
9992
  destination: "the request destination does not match its configured runtime",
9962
9993
  transport: "this provider execution path cannot enforce the residency guard",
9963
9994
  request: "this is not a supported, explicit model inference request",
@@ -9966,22 +9997,328 @@ var reasons = {
9966
9997
  configuration: "the residency guard configuration is invalid"
9967
9998
  };
9968
9999
  var modelResidencyMessages = {
9969
- blocked: (reason) => `[openteam] Lemonade model request blocked: ${reasons[reason]}.`,
9970
- providerDisabled: (providerID) => `[openteam] Lemonade provider "${providerID}" is disabled because its configured transport or runtime mapping cannot enforce loaded-only requests.`,
9971
- extractionRuntimeUnavailable: "[openteam] Memory extraction requires one enabled local runtime matching the configured extraction provider."
10000
+ blocked: (reason, runtimeLabel = "Lemonade", detail) => `[openteam] ${runtimeLabel} model request blocked: ${reasons[reason]}${detail === undefined ? "" : ` (${detail})`}.`,
10001
+ providerDisabled: (providerID, runtimeLabel = "Lemonade") => `[openteam] ${runtimeLabel} provider "${providerID}" is disabled because its configured transport or runtime mapping cannot enforce loaded-only requests.`,
10002
+ extractionRuntimeUnavailable: "[openteam] Memory extraction requires one enabled local runtime matching the configured extraction provider.",
10003
+ extractionRuntimeUnavailableFor: (runtimeLabel) => `[openteam] Memory extraction requires one enabled ${runtimeLabel} local runtime matching the configured extraction provider.`
10004
+ };
10005
+ var llamaSwapResidencyMessages = {
10006
+ catalogModelDetail: (id, detail) => `catalog model "${id}" ${detail}`,
10007
+ invalidCatalogField: (field) => `declares an invalid ${field}`,
10008
+ catalogEntryMustExposeStringId: "catalog entries must expose a string id",
10009
+ catalogModelIdsMustBeNonEmptyTrimmedStrings: "catalog model ids must be non-empty, trimmed strings",
10010
+ expectedDataArray: (path2) => `GET ${path2} must return an object with a data array`,
10011
+ duplicateCanonicalModelId: (modelID, path2) => `duplicate canonical model id "${modelID}" in ${path2}`,
10012
+ httpFailure: (path2, status) => `GET ${path2} failed with HTTP ${status}`,
10013
+ unverifiableResponse: (path2) => `GET ${path2} failed before a verifiable response`,
10014
+ expectedProfilesArray: (path2) => `GET ${path2} must return an object with a profiles array`,
10015
+ activeProfileRewrite: (profile) => `active profile "${profile}" can rewrite canonical model routing`,
10016
+ invalidActiveProfileValue: (path2) => `GET ${path2} reported an invalid active profile value`,
10017
+ expectedRunningArray: (path2) => `GET ${path2} must return an object with a running array`,
10018
+ malformedRunningEvidence: (path2) => `GET ${path2} returned malformed model/state evidence`,
10019
+ duplicateRunningEvidence: (path2, modelID) => `GET ${path2} returned duplicate evidence for "${modelID}"`
9972
10020
  };
9973
10021
 
9974
- // src/local/lemonadeResidency.ts
10022
+ // src/local/residency.ts
9975
10023
  class ModelResidencyError extends Error {
9976
10024
  code;
9977
10025
  statusCode = 400;
9978
10026
  isRetryable = false;
9979
- constructor(code) {
9980
- super(modelResidencyMessages.blocked(code));
10027
+ constructor(code, runtimeLabel = "Lemonade", detail) {
10028
+ super(modelResidencyMessages.blocked(code, runtimeLabel, detail));
9981
10029
  this.code = code;
9982
10030
  this.name = "ModelResidencyError";
9983
10031
  }
9984
10032
  }
10033
+ async function withResidencyDeadline(operation, options) {
10034
+ const timeoutMs = options.timeoutMs ?? 5000;
10035
+ const runtimeLabel = options.runtimeLabel;
10036
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
10037
+ throw new ModelResidencyError("configuration", runtimeLabel);
10038
+ }
10039
+ const controller = new AbortController;
10040
+ const cancel = () => controller.abort(new ModelResidencyError("cancelled", runtimeLabel));
10041
+ if (options.signal?.aborted)
10042
+ throw new ModelResidencyError("cancelled", runtimeLabel);
10043
+ options.signal?.addEventListener("abort", cancel, { once: true });
10044
+ const schedule = options.scheduleTimeout ?? ((callback, ms) => {
10045
+ const timer = setTimeout(callback, ms);
10046
+ return () => clearTimeout(timer);
10047
+ });
10048
+ let onAbort = () => {};
10049
+ const aborted = new Promise((_, reject) => {
10050
+ onAbort = () => reject(controller.signal.reason);
10051
+ controller.signal.addEventListener("abort", onAbort, { once: true });
10052
+ });
10053
+ const clearTimer = schedule(() => controller.abort(new ModelResidencyError("timeout", runtimeLabel)), timeoutMs);
10054
+ try {
10055
+ controller.signal.throwIfAborted();
10056
+ return await Promise.race([operation(controller.signal), aborted]);
10057
+ } finally {
10058
+ clearTimer();
10059
+ options.signal?.removeEventListener("abort", cancel);
10060
+ controller.signal.removeEventListener("abort", onAbort);
10061
+ controller.abort();
10062
+ }
10063
+ }
10064
+
10065
+ // src/local/llamaSwapResidency.ts
10066
+ function isObject2(value) {
10067
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10068
+ }
10069
+ function readPositiveSafeInteger(value) {
10070
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
10071
+ }
10072
+ function readSupportsTools2(model) {
10073
+ const direct = readBoolean2(model, [
10074
+ "tool_call",
10075
+ "tool_calls",
10076
+ "toolCalling",
10077
+ "supportsToolCalling",
10078
+ "supports_tools",
10079
+ "supportsTools"
10080
+ ]);
10081
+ if (direct !== undefined) {
10082
+ return direct;
10083
+ }
10084
+ const capabilities = readCapabilityBoolean2(model.capabilities);
10085
+ if (capabilities !== undefined) {
10086
+ return capabilities;
10087
+ }
10088
+ const features = readCapabilityBoolean2(model.features);
10089
+ if (features !== undefined) {
10090
+ return features;
10091
+ }
10092
+ const labels = readCapabilityBoolean2(model.labels);
10093
+ if (labels !== undefined) {
10094
+ return labels;
10095
+ }
10096
+ return "unknown";
10097
+ }
10098
+ function readCapabilityBoolean2(value) {
10099
+ if (Array.isArray(value)) {
10100
+ if (value.some((entry) => typeof entry === "string" && ["tools", "tool_call", "tool-calling", "function_calling"].includes(entry))) {
10101
+ return true;
10102
+ }
10103
+ return;
10104
+ }
10105
+ if (!isObject2(value)) {
10106
+ return;
10107
+ }
10108
+ return readBoolean2(value, [
10109
+ "tools",
10110
+ "tool_call",
10111
+ "tool_calls",
10112
+ "toolCalling",
10113
+ "function_calling",
10114
+ "supportsToolCalling"
10115
+ ]);
10116
+ }
10117
+ function readBoolean2(value, keys) {
10118
+ for (const key of keys) {
10119
+ if (typeof value[key] === "boolean") {
10120
+ return value[key];
10121
+ }
10122
+ }
10123
+ return;
10124
+ }
10125
+ function modelDetail(id, detail) {
10126
+ return llamaSwapResidencyMessages.catalogModelDetail(id, detail);
10127
+ }
10128
+ function llamaSwapBaseURL(value) {
10129
+ let url;
10130
+ try {
10131
+ url = new URL(value);
10132
+ } catch {
10133
+ throw new ModelResidencyError("destination", "llama-swap");
10134
+ }
10135
+ if (url.protocol !== "http:" && url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") {
10136
+ throw new ModelResidencyError("destination", "llama-swap");
10137
+ }
10138
+ if (url.pathname === "/" || url.pathname === "") {
10139
+ return `${url.origin}/v1`;
10140
+ }
10141
+ if (url.pathname === "/v1" || url.pathname === "/v1/") {
10142
+ return `${url.origin}/v1`;
10143
+ }
10144
+ throw new ModelResidencyError("destination", "llama-swap");
10145
+ }
10146
+ function parseCanonicalType(entry, id) {
10147
+ if (!isObject2(entry.meta) || !isObject2(entry.meta.llamaswap)) {
10148
+ return;
10149
+ }
10150
+ const type = entry.meta.llamaswap.type;
10151
+ if (type === undefined) {
10152
+ return;
10153
+ }
10154
+ if (typeof type !== "string" || type.length === 0 || type !== type.trim()) {
10155
+ throw new ModelResidencyError("unknown", "llama-swap", modelDetail(id, llamaSwapResidencyMessages.invalidCatalogField("meta.llamaswap.type")));
10156
+ }
10157
+ return type === "model" ? "model" : undefined;
10158
+ }
10159
+ function readContextCandidates(entry) {
10160
+ const values = [
10161
+ readPositiveSafeInteger(entry.context_length),
10162
+ readPositiveSafeInteger(entry.context_window),
10163
+ isObject2(entry.meta) ? readPositiveSafeInteger(entry.meta.n_ctx) : undefined
10164
+ ];
10165
+ return values.filter((value) => value !== undefined);
10166
+ }
10167
+ function readDeclaredMaxOutput(entry, id) {
10168
+ if (!isObject2(entry.meta) || !isObject2(entry.meta.llamaswap)) {
10169
+ return;
10170
+ }
10171
+ if (!Object.hasOwn(entry.meta.llamaswap, "max_output_tokens")) {
10172
+ return;
10173
+ }
10174
+ const maxOutputTokens = readPositiveSafeInteger(entry.meta.llamaswap.max_output_tokens);
10175
+ if (maxOutputTokens === undefined) {
10176
+ throw new ModelResidencyError("unknown", "llama-swap", modelDetail(id, llamaSwapResidencyMessages.invalidCatalogField("meta.llamaswap.max_output_tokens")));
10177
+ }
10178
+ return maxOutputTokens;
10179
+ }
10180
+ function parseCatalogModel(value) {
10181
+ if (!isObject2(value) || typeof value.id !== "string") {
10182
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.catalogEntryMustExposeStringId);
10183
+ }
10184
+ const id = value.id;
10185
+ if (id.length === 0 || id !== id.trim()) {
10186
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.catalogModelIdsMustBeNonEmptyTrimmedStrings);
10187
+ }
10188
+ if (parseCanonicalType(value, id) !== "model") {
10189
+ return;
10190
+ }
10191
+ const model = {
10192
+ modelID: id,
10193
+ supportsTools: readSupportsTools2(value)
10194
+ };
10195
+ const contexts = readContextCandidates(value);
10196
+ if (contexts.length > 0) {
10197
+ model.contextWindow = Math.min(...contexts);
10198
+ model.contextWindowProvenance = "declared";
10199
+ }
10200
+ const maxOutputTokens = readDeclaredMaxOutput(value, id);
10201
+ if (maxOutputTokens !== undefined) {
10202
+ model.maxOutputTokens = maxOutputTokens;
10203
+ model.maxOutputTokensProvenance = "declared";
10204
+ }
10205
+ return model;
10206
+ }
10207
+ function parseCatalog(payload) {
10208
+ if (!isObject2(payload) || !Array.isArray(payload.data)) {
10209
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.expectedDataArray("/v1/models"));
10210
+ }
10211
+ const seen = new Set;
10212
+ const models = [];
10213
+ for (const entry of payload.data) {
10214
+ const model = parseCatalogModel(entry);
10215
+ if (model === undefined) {
10216
+ continue;
10217
+ }
10218
+ if (seen.has(model.modelID)) {
10219
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.duplicateCanonicalModelId(model.modelID, "/v1/models"));
10220
+ }
10221
+ seen.add(model.modelID);
10222
+ models.push(model);
10223
+ }
10224
+ return models;
10225
+ }
10226
+ async function metadata(url, fetch, signal, headers) {
10227
+ signal.throwIfAborted();
10228
+ try {
10229
+ const response = await fetch(url, {
10230
+ method: "GET",
10231
+ headers,
10232
+ signal,
10233
+ redirect: "error",
10234
+ cache: "no-store"
10235
+ });
10236
+ signal.throwIfAborted();
10237
+ if (response.status === 401 || response.status === 403) {
10238
+ throw new ModelResidencyError("authentication-unsupported", "llama-swap");
10239
+ }
10240
+ if (!response.ok || response.redirected || response.url !== "" && response.url !== url) {
10241
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.httpFailure(new URL(url).pathname, response.status));
10242
+ }
10243
+ return await response.json();
10244
+ } catch (error) {
10245
+ signal.throwIfAborted();
10246
+ if (error instanceof ModelResidencyError) {
10247
+ throw error;
10248
+ }
10249
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.unverifiableResponse(new URL(url).pathname));
10250
+ }
10251
+ }
10252
+ function readProfiles(payload) {
10253
+ if (!isObject2(payload) || !Array.isArray(payload.profiles)) {
10254
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.expectedProfilesArray("/api/profiles"));
10255
+ }
10256
+ if (payload.active === null) {
10257
+ return;
10258
+ }
10259
+ if (typeof payload.active === "string" && payload.active.length > 0 && payload.active === payload.active.trim()) {
10260
+ throw new ModelResidencyError("identity", "llama-swap", llamaSwapResidencyMessages.activeProfileRewrite(payload.active));
10261
+ }
10262
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.invalidActiveProfileValue("/api/profiles"));
10263
+ }
10264
+ function readRunning(payload, modelIDs) {
10265
+ if (!isObject2(payload) || !Array.isArray(payload.running)) {
10266
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.expectedRunningArray("/running"));
10267
+ }
10268
+ const result = new Map;
10269
+ for (const modelID of modelIDs) {
10270
+ result.set(modelID, { loaded: false });
10271
+ }
10272
+ const seen = new Set;
10273
+ for (const entry of payload.running) {
10274
+ if (!isObject2(entry) || typeof entry.model !== "string" || entry.model.length === 0 || entry.model !== entry.model.trim() || typeof entry.state !== "string" || entry.state.length === 0 || entry.state !== entry.state.trim()) {
10275
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.malformedRunningEvidence("/running"));
10276
+ }
10277
+ if (seen.has(entry.model)) {
10278
+ throw new ModelResidencyError("unknown", "llama-swap", llamaSwapResidencyMessages.duplicateRunningEvidence("/running", entry.model));
10279
+ }
10280
+ seen.add(entry.model);
10281
+ if (!modelIDs.has(entry.model)) {
10282
+ continue;
10283
+ }
10284
+ result.set(entry.model, { loaded: entry.state === "ready" });
10285
+ }
10286
+ return result;
10287
+ }
10288
+ function requestHeaders(headers) {
10289
+ const result = new Headers(headers);
10290
+ result.delete("content-type");
10291
+ result.delete("content-length");
10292
+ result.set("accept", "application/json");
10293
+ result.set("cache-control", "no-cache, no-store");
10294
+ result.delete("if-none-match");
10295
+ result.delete("if-modified-since");
10296
+ return result;
10297
+ }
10298
+ function llamaSwapResidencyOptions(options) {
10299
+ return { ...options, runtimeLabel: "llama-swap" };
10300
+ }
10301
+ async function readLlamaSwapCatalog(baseURL, fetch, options = {}) {
10302
+ const base = llamaSwapBaseURL(baseURL);
10303
+ const headers = requestHeaders(options.headers);
10304
+ return withResidencyDeadline(async (signal) => parseCatalog(await metadata(`${base}/models`, fetch, signal, headers)), llamaSwapResidencyOptions(options));
10305
+ }
10306
+ async function readLlamaSwapResidency(baseURL, fetch, options = {}) {
10307
+ const base = llamaSwapBaseURL(baseURL);
10308
+ const origin = new URL(base).origin;
10309
+ const headers = requestHeaders(options.headers);
10310
+ return withResidencyDeadline(async (signal) => {
10311
+ const models = await readLlamaSwapCatalog(base, fetch, {
10312
+ ...llamaSwapResidencyOptions(options),
10313
+ signal
10314
+ });
10315
+ readProfiles(await metadata(`${origin}/api/profiles`, fetch, signal, headers));
10316
+ return readRunning(await metadata(`${origin}/running`, fetch, signal, headers), new Set(models.map((model) => model.modelID)));
10317
+ }, llamaSwapResidencyOptions(options));
10318
+ }
10319
+
10320
+ // src/local/lemonadeResidency.ts
10321
+ import { z as z19 } from "zod";
9985
10322
  var modelName = z19.string().min(1).refine((name) => name === name.trim());
9986
10323
  var LoadedModelSchema = z19.object({
9987
10324
  model_name: modelName,
@@ -10118,37 +10455,7 @@ function lemonadeBaseURL(value) {
10118
10455
  }
10119
10456
  return `${url.origin}${url.pathname.replace(/\/$/, "")}`;
10120
10457
  }
10121
- async function withResidencyDeadline(operation, options) {
10122
- const timeoutMs = options.timeoutMs ?? 5000;
10123
- if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
10124
- throw new ModelResidencyError("configuration");
10125
- }
10126
- const controller = new AbortController;
10127
- const cancel = () => controller.abort(new ModelResidencyError("cancelled"));
10128
- if (options.signal?.aborted)
10129
- throw new ModelResidencyError("cancelled");
10130
- options.signal?.addEventListener("abort", cancel, { once: true });
10131
- const schedule = options.scheduleTimeout ?? ((callback, ms) => {
10132
- const timer = setTimeout(callback, ms);
10133
- return () => clearTimeout(timer);
10134
- });
10135
- let onAbort = () => {};
10136
- const aborted = new Promise((_, reject) => {
10137
- onAbort = () => reject(controller.signal.reason);
10138
- controller.signal.addEventListener("abort", onAbort, { once: true });
10139
- });
10140
- const clearTimer = schedule(() => controller.abort(new ModelResidencyError("timeout")), timeoutMs);
10141
- try {
10142
- controller.signal.throwIfAborted();
10143
- return await Promise.race([operation(controller.signal), aborted]);
10144
- } finally {
10145
- clearTimer();
10146
- options.signal?.removeEventListener("abort", cancel);
10147
- controller.signal.removeEventListener("abort", onAbort);
10148
- controller.abort();
10149
- }
10150
- }
10151
- async function metadata(url, fetch, signal, headers) {
10458
+ async function metadata2(url, fetch, signal, headers) {
10152
10459
  signal.throwIfAborted();
10153
10460
  let payload;
10154
10461
  try {
@@ -10181,8 +10488,8 @@ async function readLemonadeResidency(baseURL, fetch, options = {}) {
10181
10488
  headers.delete("if-none-match");
10182
10489
  headers.delete("if-modified-since");
10183
10490
  return withResidencyDeadline(async (signal) => {
10184
- const aliases = await metadata(`${new URL(base).origin}/internal/aliases`, fetch, signal, headers);
10185
- const health = await metadata(`${base}/health`, fetch, signal, headers);
10491
+ const aliases = await metadata2(`${new URL(base).origin}/internal/aliases`, fetch, signal, headers);
10492
+ const health = await metadata2(`${base}/health`, fetch, signal, headers);
10186
10493
  return parseResidency(health, aliases);
10187
10494
  }, options);
10188
10495
  }
@@ -10253,11 +10560,11 @@ async function listLemonadeModels(baseURL, fetch) {
10253
10560
  }
10254
10561
  function readNonTextModelIDs(payload) {
10255
10562
  const ids = new Set;
10256
- if (!isObject2(payload) || !Array.isArray(payload.data)) {
10563
+ if (!isObject3(payload) || !Array.isArray(payload.data)) {
10257
10564
  return ids;
10258
10565
  }
10259
10566
  for (const entry of payload.data) {
10260
- if (!isObject2(entry) || typeof entry.id !== "string") {
10567
+ if (!isObject3(entry) || typeof entry.id !== "string") {
10261
10568
  continue;
10262
10569
  }
10263
10570
  if (isNonTextModel(entry)) {
@@ -10295,7 +10602,7 @@ function readLoadedContextWindow(entry) {
10295
10602
  }
10296
10603
  function slotsFromLlamacppArgs(entry) {
10297
10604
  const options = entry.recipe_options;
10298
- if (!isObject2(options) || typeof options.llamacpp_args !== "string") {
10605
+ if (!isObject3(options) || typeof options.llamacpp_args !== "string") {
10299
10606
  return;
10300
10607
  }
10301
10608
  const match = PARALLEL_ARG.exec(options.llamacpp_args);
@@ -10331,7 +10638,7 @@ function slotsFromLaunchCommand(entry) {
10331
10638
  }
10332
10639
  function contextWindowFromRecipeOptions(entry) {
10333
10640
  const options = entry.recipe_options;
10334
- if (!isObject2(options) || !hasLlamacppEvidence(entry)) {
10641
+ if (!isObject3(options) || !hasLlamacppEvidence(entry)) {
10335
10642
  return;
10336
10643
  }
10337
10644
  const direct = positiveIntegerValue(options.ctx_size);
@@ -10391,7 +10698,7 @@ function hasLlamacppEvidence(entry) {
10391
10698
  return true;
10392
10699
  }
10393
10700
  const options = entry.recipe_options;
10394
- if (isObject2(options) && typeof options.llamacpp_args === "string") {
10701
+ if (isObject3(options) && typeof options.llamacpp_args === "string") {
10395
10702
  return true;
10396
10703
  }
10397
10704
  return Array.isArray(entry.launch_command) && launchCommandLooksLikeLlamacpp(entry.launch_command);
@@ -10447,10 +10754,57 @@ function enrichWithLoadState(models, loaded) {
10447
10754
  };
10448
10755
  });
10449
10756
  }
10450
- function isObject2(value) {
10757
+ function isObject3(value) {
10451
10758
  return typeof value === "object" && value !== null && !Array.isArray(value);
10452
10759
  }
10453
10760
 
10761
+ // src/local/llamaSwap.ts
10762
+ var LLAMA_SWAP_DEFAULT_BASE_URL = "http://localhost:8080/v1";
10763
+ function withLoadedState(models, residency) {
10764
+ return models.map((model) => {
10765
+ const loaded = residency.get(model.modelID)?.loaded;
10766
+ return loaded === undefined ? model : { ...model, loaded };
10767
+ });
10768
+ }
10769
+ function createLlamaSwapAdapter() {
10770
+ const listModels = async (options) => {
10771
+ const baseURL = llamaSwapBaseURL(options.baseURL ?? LLAMA_SWAP_DEFAULT_BASE_URL);
10772
+ const models = await readLlamaSwapCatalog(baseURL, options.fetch);
10773
+ try {
10774
+ return withLoadedState(models, await readLlamaSwapResidency(baseURL, options.fetch));
10775
+ } catch (error) {
10776
+ if (error instanceof ModelResidencyError && (error.code === "identity" || error.code === "unknown")) {
10777
+ return models;
10778
+ }
10779
+ throw error;
10780
+ }
10781
+ };
10782
+ return {
10783
+ id: "llama-swap",
10784
+ defaultBaseURL: LLAMA_SWAP_DEFAULT_BASE_URL,
10785
+ listModels,
10786
+ async probe(options) {
10787
+ const baseURL = llamaSwapBaseURL(options.baseURL ?? LLAMA_SWAP_DEFAULT_BASE_URL);
10788
+ try {
10789
+ return {
10790
+ id: "llama-swap",
10791
+ baseURL,
10792
+ reachable: true,
10793
+ models: await listModels({ ...options, baseURL }),
10794
+ probedAt: options.probedAt
10795
+ };
10796
+ } catch (error) {
10797
+ return unavailableSnapshot({
10798
+ id: "llama-swap",
10799
+ baseURL,
10800
+ probedAt: options.probedAt,
10801
+ error
10802
+ });
10803
+ }
10804
+ }
10805
+ };
10806
+ }
10807
+
10454
10808
  // src/local/lmstudio.ts
10455
10809
  var LMSTUDIO_DEFAULT_BASE_URL = "http://localhost:1234/v1";
10456
10810
  function createLMStudioAdapter() {
@@ -10503,6 +10857,7 @@ class RuntimeRegistry {
10503
10857
  lmstudio: createLMStudioAdapter(),
10504
10858
  "foundry-local": createFoundryLocalAdapter(),
10505
10859
  lemonade: createLemonadeAdapter(),
10860
+ "llama-swap": createLlamaSwapAdapter(),
10506
10861
  ...options.adapters
10507
10862
  };
10508
10863
  this.clock = options.clock;
@@ -10969,7 +11324,7 @@ var TRUNCATED_REASON_SUFFIX = "…[truncated]";
10969
11324
  var MAX_SERIALIZED_ARRAY_ITEMS = 20;
10970
11325
  var MAX_SERIALIZED_OBJECT_PROPERTIES = 25;
10971
11326
  var MAX_SERIALIZED_DEPTH = 4;
10972
- function isObject3(value) {
11327
+ function isObject4(value) {
10973
11328
  return typeof value === "object" && value !== null;
10974
11329
  }
10975
11330
  function isError(value) {
@@ -10999,7 +11354,7 @@ function stringProperty(value, key) {
10999
11354
  }
11000
11355
  function nestedMessage(value, key) {
11001
11356
  const container = safeGet(value, key);
11002
- if (!isObject3(container)) {
11357
+ if (!isObject4(container)) {
11003
11358
  return;
11004
11359
  }
11005
11360
  const message = stringProperty(container, "message");
@@ -11083,7 +11438,7 @@ function errorReason(error) {
11083
11438
  return message;
11084
11439
  }
11085
11440
  }
11086
- if (isObject3(error)) {
11441
+ if (isObject4(error)) {
11087
11442
  const message = stringProperty(error, "message");
11088
11443
  if (message !== undefined) {
11089
11444
  return message;
@@ -11101,7 +11456,7 @@ function errorReason(error) {
11101
11456
  return safeToString(error);
11102
11457
  }
11103
11458
  function isNormalizedSessionFailure(value) {
11104
- return isObject3(value) && safeGet(value, "kind") === "normalized-session-failure" && typeof safeGet(value, "failureClass") === "string" && typeof safeGet(value, "retryable") === "boolean";
11459
+ return isObject4(value) && safeGet(value, "kind") === "normalized-session-failure" && typeof safeGet(value, "failureClass") === "string" && typeof safeGet(value, "retryable") === "boolean";
11105
11460
  }
11106
11461
  function createArgs(req) {
11107
11462
  const body = {};
@@ -11228,7 +11583,7 @@ async function confirmSessionStopped(client, sessionID, directory, deps, deadlin
11228
11583
  return { confirmed: false, cause: "status-error" };
11229
11584
  }
11230
11585
  const statuses = statusOutcome.value;
11231
- if (!isObject3(statuses)) {
11586
+ if (!isObject4(statuses)) {
11232
11587
  return { confirmed: false, cause: "status-error" };
11233
11588
  }
11234
11589
  if (safeGet(statuses, "error") !== undefined) {
@@ -11378,8 +11733,11 @@ async function runSubsession(client, req, deps) {
11378
11733
  }
11379
11734
 
11380
11735
  // src/local/modelResidency.ts
11736
+ function runtimeRequiresModelResidency(kind) {
11737
+ return kind === "lemonade" || kind === "llama-swap";
11738
+ }
11381
11739
  function hasRequiredModelResidency(kind, model) {
11382
- return kind !== "lemonade" || model?.loaded === true;
11740
+ return !runtimeRequiresModelResidency(kind) || model?.loaded === true;
11383
11741
  }
11384
11742
 
11385
11743
  // src/plugin/availability.ts
@@ -11388,9 +11746,9 @@ function modelKey2(model) {
11388
11746
  }
11389
11747
  function availabilityMetadata({
11390
11748
  apiModelID: _apiModelID,
11391
- ...metadata2
11749
+ ...metadata3
11392
11750
  }) {
11393
- return metadata2;
11751
+ return metadata3;
11394
11752
  }
11395
11753
  function compareAvailableModels(left, right) {
11396
11754
  const kindOrder = Number(left.kind === "frontier") - Number(right.kind === "frontier");
@@ -11410,7 +11768,7 @@ function isConfiguredLocalModel(config, model) {
11410
11768
  return config.local.runtimes.some((runtime) => runtime.defaultModel.providerID === model.providerID);
11411
11769
  }
11412
11770
  function requiresModelResidency(config, model) {
11413
- return config.local.runtimes.some((runtime) => runtime.defaultModel.providerID === model.providerID && !hasRequiredModelResidency(localRuntimeKind(runtime), undefined));
11771
+ return config.local.runtimes.some((runtime) => runtime.defaultModel.providerID === model.providerID && runtimeRequiresModelResidency(localRuntimeKind(runtime)));
11414
11772
  }
11415
11773
  function configuredFrontierProviderIDs(config) {
11416
11774
  const providerIDs = new Set([config.baseline.hardDefault.providerID]);
@@ -12036,6 +12394,7 @@ var PROVIDER_LABELS = {
12036
12394
  ollama: "Ollama",
12037
12395
  lmstudio: "LM Studio",
12038
12396
  lemonade: "Lemonade Server",
12397
+ "llama-swap": executionSetupMessages.llamaSwap.label,
12039
12398
  "foundry-local": "Foundry Local"
12040
12399
  };
12041
12400
  function providerLabel(providerID) {
@@ -12257,7 +12616,8 @@ var KNOWN_LOCAL_PROVIDER_IDS = new Set([
12257
12616
  "ollama",
12258
12617
  "lmstudio",
12259
12618
  "foundry-local",
12260
- "lemonade"
12619
+ "lemonade",
12620
+ "llama-swap"
12261
12621
  ]);
12262
12622
  var KNOWN_FRONTIER_PROVIDER_IDS = new Set([
12263
12623
  "github-copilot",
@@ -12603,7 +12963,7 @@ function runtimeLine(snapshot, runtime) {
12603
12963
  const mark = snapshot.reachable ? "✓" : "✗";
12604
12964
  const detail = snapshot.reachable ? `${snapshot.models.length} model(s)` : snapshot.error ?? "unreachable";
12605
12965
  const declared = runtime?.maxConcurrency;
12606
- const slots = declared === undefined ? ` · ${DEFAULT_LOCAL_MAX_CONCURRENCY} slot(s) (default)` : ` · ${declared} slot(s) (declared)`;
12966
+ const slots = declared === undefined ? doctorMessages.defaultRuntimeSlots(effectiveMaxConcurrency(runtime ?? {})) : ` · ${declared} slot(s) (declared)`;
12607
12967
  return ` ${mark} ${snapshot.id.padEnd(14)} ${snapshot.baseURL || "(no baseURL)"} — ${detail}${slots}`;
12608
12968
  }
12609
12969
  function runtimeModelSlotsLine(runtime, snapshot) {
@@ -12615,6 +12975,8 @@ function runtimeModelSlotsLine(runtime, snapshot) {
12615
12975
  if (runtime === undefined || snapshot === undefined || !snapshot.reachable || snapshot.models.length === 0) {
12616
12976
  return [];
12617
12977
  }
12978
+ if (localRuntimeKind(runtime) === "llama-swap")
12979
+ return [doctorMessages.llamaSwapConcurrency];
12618
12980
  const reported = snapshot.models.flatMap((model) => model.slots === undefined ? [] : [`${model.modelID} ${model.slots} slot(s)`]);
12619
12981
  if (reported.length > 0) {
12620
12982
  return [
@@ -13018,7 +13380,7 @@ function renderDoctor(input) {
13018
13380
  continue;
13019
13381
  }
13020
13382
  const runtime = enabledRuntimes.find((r) => r.id === snapshot.id);
13021
- const cap = runtime?.maxConcurrency ?? DEFAULT_LOCAL_MAX_CONCURRENCY;
13383
+ const cap = effectiveMaxConcurrency(runtime ?? {});
13022
13384
  lines.push(` ⚠ ${snapshot.id} is reached over the network: its ${cap}-slot cap is enforced per openteam process, so`, " two processes at once (an opencode session plus 'openteam console') can together exceed it.");
13023
13385
  }
13024
13386
  lines.push(` telemetry: ${input.telemetryPath} — ${input.telemetryRecords} record(s)`);
@@ -13630,6 +13992,38 @@ function missingGeneratedStateRules(gitignore) {
13630
13992
  const present = new Set(activeGitignoreRules(gitignore));
13631
13993
  return OPENCODE_GITIGNORE_GENERATED_STATE_RULES.filter((rule) => !present.has(rule));
13632
13994
  }
13995
+ var KNOWN_RUNTIMES = [
13996
+ {
13997
+ id: "ollama",
13998
+ label: "Ollama",
13999
+ defaultBaseURL: "http://localhost:11434/v1",
14000
+ fallbackModelID: "qwen3:8b"
14001
+ },
14002
+ {
14003
+ id: "lmstudio",
14004
+ label: "LM Studio",
14005
+ defaultBaseURL: "http://localhost:1234/v1",
14006
+ fallbackModelID: "qwen2.5-coder"
14007
+ },
14008
+ {
14009
+ id: "lemonade",
14010
+ label: "Lemonade Server",
14011
+ defaultBaseURL: LEMONADE_DEFAULT_BASE_URL,
14012
+ fallbackModelID: "Qwen3-Coder-30B-A3B-Instruct-GGUF"
14013
+ },
14014
+ {
14015
+ id: "llama-swap",
14016
+ label: executionSetupMessages.llamaSwap.label,
14017
+ defaultBaseURL: LLAMA_SWAP_DEFAULT_BASE_URL,
14018
+ fallbackModelID: ""
14019
+ },
14020
+ {
14021
+ id: "foundry-local",
14022
+ label: "Foundry Local",
14023
+ fallbackModelID: "Phi-4-mini-instruct",
14024
+ dynamicPort: true
14025
+ }
14026
+ ];
13633
14027
 
13634
14028
  // src/commands/purge.ts
13635
14029
  var PURGE_CATEGORY_IDS = [
@@ -14658,7 +15052,7 @@ function liveModelsFromSnapshots(snapshots, config) {
14658
15052
  map.set(runtime.defaultModel.providerID, {
14659
15053
  reachable: snapshot.reachable,
14660
15054
  modelIDs: new Set(snapshot.models.filter((model) => hasRequiredModelResidency(kind, model)).map((model) => model.modelID)),
14661
- ...kind === "lemonade" ? {
15055
+ ...runtimeRequiresModelResidency(kind) ? {
14662
15056
  residency: new Map(snapshot.models.map((model) => [model.modelID, model.loaded]))
14663
15057
  } : {}
14664
15058
  });
@@ -14988,9 +15382,9 @@ async function runCli(argv, deps) {
14988
15382
  const inventoryNotes = [];
14989
15383
  const readySnapshots = snapshots.map((snapshot) => {
14990
15384
  const runtime = config.local.runtimes.find((runtime2) => runtime2.id === snapshot.id);
14991
- if (runtime === undefined || localRuntimeKind(runtime) !== "lemonade")
15385
+ if (runtime === undefined || !runtimeRequiresModelResidency(localRuntimeKind(runtime)))
14992
15386
  return snapshot;
14993
- inventoryNotes.push(executionSetupMessages.lemonadeInventory(runtime.id, runtime.defaultModel.providerID, snapshot.models));
15387
+ inventoryNotes.push(executionSetupMessages.lemonadeInventory(runtime.id, runtime.defaultModel.providerID, snapshot.models, localRuntimeKind(runtime) === "llama-swap" ? "llama-swap" : "Lemonade"));
14994
15388
  return {
14995
15389
  ...snapshot,
14996
15390
  models: snapshot.models.filter((model) => hasRequiredModelResidency(localRuntimeKind(runtime), model))
@@ -15001,7 +15395,7 @@ async function runCli(argv, deps) {
15001
15395
  if (agent.model === undefined || live !== "not-loaded" && live !== "residency-unknown")
15002
15396
  return [];
15003
15397
  return [
15004
- executionSetupMessages.lemonadeAgentResidency(agent.name, `${agent.model.providerID}/${agent.model.modelID}`, live)
15398
+ executionSetupMessages.lemonadeAgentResidency(agent.name, `${agent.model.providerID}/${agent.model.modelID}`, live, config.local.runtimes.some((runtime) => runtime.defaultModel.providerID === agent.model?.providerID && localRuntimeKind(runtime) === "llama-swap") ? "llama-swap" : "Lemonade")
15005
15399
  ];
15006
15400
  });
15007
15401
  return {
@@ -17231,6 +17625,84 @@ function createLemonadeGuardedFetch(options) {
17231
17625
  });
17232
17626
  }
17233
17627
 
17628
+ // src/local/llamaSwapRequestGuard.ts
17629
+ var inferencePaths2 = new Set([
17630
+ "/chat/completions",
17631
+ "/completions",
17632
+ "/embeddings",
17633
+ "/responses"
17634
+ ]);
17635
+ async function validatedModelID(payload, modelIDs) {
17636
+ if (typeof payload !== "object" || payload === null || !("model" in payload) || typeof payload.model !== "string" || payload.model.length === 0 || payload.model !== payload.model.trim()) {
17637
+ throw new ModelResidencyError("request", "llama-swap");
17638
+ }
17639
+ if (modelIDs === undefined) {
17640
+ return payload.model;
17641
+ }
17642
+ const ids = typeof modelIDs === "function" ? await modelIDs() : modelIDs;
17643
+ if (ids.has(payload.model)) {
17644
+ return payload.model;
17645
+ }
17646
+ throw new ModelResidencyError("identity", "llama-swap");
17647
+ }
17648
+ function createLlamaSwapGuardedFetch(options) {
17649
+ const base = llamaSwapBaseURL(options.baseURL);
17650
+ const upstream = options.fetch;
17651
+ const healthFetch = options.healthFetch ?? upstream;
17652
+ return Object.assign(async (input, init) => {
17653
+ const request = new Request(input, init);
17654
+ const url = new URL(request.url);
17655
+ if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "" || !request.url.startsWith(`${base}/`)) {
17656
+ throw new ModelResidencyError("destination", "llama-swap");
17657
+ }
17658
+ const path4 = request.url.slice(base.length);
17659
+ const metadataOnly = request.method === "GET" && /^\/models(?:\/[^/]+)?$/.test(path4);
17660
+ if (!metadataOnly && (request.method !== "POST" || !inferencePaths2.has(path4))) {
17661
+ throw new ModelResidencyError("request", "llama-swap");
17662
+ }
17663
+ if (!metadataOnly) {
17664
+ await withResidencyDeadline(async (signal) => {
17665
+ signal.throwIfAborted();
17666
+ let payload;
17667
+ try {
17668
+ payload = await request.clone().json();
17669
+ } catch {
17670
+ signal.throwIfAborted();
17671
+ throw new ModelResidencyError("request", "llama-swap");
17672
+ }
17673
+ signal.throwIfAborted();
17674
+ const model = await validatedModelID(payload, options.modelIDs);
17675
+ const residency = await readLlamaSwapResidency(base, healthFetch, {
17676
+ ...options,
17677
+ signal,
17678
+ headers: request.headers
17679
+ });
17680
+ signal.throwIfAborted();
17681
+ if (!residency.has(model)) {
17682
+ throw new ModelResidencyError("identity", "llama-swap");
17683
+ }
17684
+ if (residency.get(model)?.loaded !== true) {
17685
+ throw new ModelResidencyError("inactive", "llama-swap");
17686
+ }
17687
+ }, { ...options, signal: request.signal, runtimeLabel: "llama-swap" });
17688
+ }
17689
+ if (request.signal.aborted) {
17690
+ throw new ModelResidencyError("cancelled", "llama-swap");
17691
+ }
17692
+ const {
17693
+ body: _body,
17694
+ headers: _headers,
17695
+ method: _method,
17696
+ signal: _signal,
17697
+ redirect: _redirect,
17698
+ ...transportOptions
17699
+ } = init ?? {};
17700
+ return upstream(request, { ...transportOptions, redirect: "error" });
17701
+ }, {
17702
+ preconnect: (...args) => upstream.preconnect?.(...args)
17703
+ });
17704
+ }
17705
+
17234
17706
  // src/plugin/modelResidency.ts
17235
17707
  function isFetch(value) {
17236
17708
  return typeof value === "function";
@@ -17270,20 +17742,29 @@ function providerRoutes(provider) {
17270
17742
  npm: model?.provider?.npm ?? provider.npm
17271
17743
  }));
17272
17744
  }
17273
- function wireID(key, model) {
17745
+ function wireID(key, model, runtimeLabel = "Lemonade") {
17274
17746
  const id = model?.id ?? key;
17275
17747
  if (id.length === 0 || id !== id.trim())
17276
- throw new ModelResidencyError("identity");
17748
+ throw new ModelResidencyError("identity", runtimeLabel);
17277
17749
  return id;
17278
17750
  }
17279
17751
  function nativeEligibleID(id) {
17280
17752
  return id === "openai" || id === "anthropic" || id.startsWith("opencode");
17281
17753
  }
17754
+ function normalizedBase(kind, baseURL) {
17755
+ return kind === "llama-swap" ? llamaSwapBaseURL(baseURL) : lemonadeBaseURL(baseURL);
17756
+ }
17757
+ function runtimeBase(runtime) {
17758
+ return runtime.baseURL ?? (localRuntimeKind(runtime) === "llama-swap" ? LLAMA_SWAP_DEFAULT_BASE_URL : LEMONADE_DEFAULT_BASE_URL);
17759
+ }
17760
+ function residencyRuntimeLabel(config, providerID) {
17761
+ return config.local.runtimes.some((runtime) => runtime.defaultModel.providerID === providerID && localRuntimeKind(runtime) === "llama-swap") ? "llama-swap" : "Lemonade";
17762
+ }
17282
17763
  function configuredRuntime(config, providerID) {
17283
17764
  const runtimes = config.local.runtimes.filter((runtime2) => runtime2.defaultModel.providerID === providerID);
17284
17765
  const runtime = runtimes[0];
17285
17766
  if (runtimes.length !== 1 || runtime === undefined || !runtime.enabled) {
17286
- throw new ModelResidencyError("destination");
17767
+ throw new ModelResidencyError("destination", residencyRuntimeLabel(config, providerID));
17287
17768
  }
17288
17769
  return runtime;
17289
17770
  }
@@ -17292,8 +17773,9 @@ function createModelResidencyGuard(config, options) {
17292
17773
  const blocked = new Set;
17293
17774
  let hostConfig;
17294
17775
  const readEnv = options.readEnv ?? ((name) => process.env[name]);
17295
- const lemonadeRuntimes = () => config.local.runtimes.filter((runtime) => localRuntimeKind(runtime) === "lemonade");
17296
- const protects = (providerID, baseURL) => lemonadeRuntimes().some((runtime) => {
17776
+ const failure = (code, providerID) => new ModelResidencyError(code, residencyRuntimeLabel(config, providerID));
17777
+ const residentRuntimes = () => config.local.runtimes.filter((runtime) => runtimeRequiresModelResidency(localRuntimeKind(runtime)));
17778
+ const protects = (providerID, baseURL) => residentRuntimes().some((runtime) => {
17297
17779
  if (runtime.defaultModel.providerID === providerID)
17298
17780
  return true;
17299
17781
  if (baseURL === undefined)
@@ -17301,13 +17783,13 @@ function createModelResidencyGuard(config, options) {
17301
17783
  const binding = endpointBinding(baseURL, readEnv);
17302
17784
  if (binding.ambiguous)
17303
17785
  return true;
17304
- return binding.origin !== undefined && binding.origin === origin(runtime.baseURL ?? LEMONADE_DEFAULT_BASE_URL);
17786
+ return binding.origin !== undefined && binding.origin === origin(runtimeBase(runtime));
17305
17787
  });
17306
17788
  const resolvedWireID = (model, declaredID) => {
17307
17789
  const id = options.resolveApiModelID === undefined ? declaredID : options.resolveApiModelID(model);
17308
17790
  if (id === undefined)
17309
- throw new ModelResidencyError("identity");
17310
- return wireID(id, undefined);
17791
+ throw failure("identity", model.providerID);
17792
+ return wireID(id, undefined, residencyRuntimeLabel(config, model.providerID));
17311
17793
  };
17312
17794
  const configHook = async (host) => {
17313
17795
  hostConfig = host;
@@ -17327,21 +17809,22 @@ function createModelResidencyGuard(config, options) {
17327
17809
  ...new Set([...host.disabled_providers ?? [], providerID])
17328
17810
  ];
17329
17811
  const reject = Object.assign(async () => {
17330
- throw new ModelResidencyError("transport");
17812
+ throw failure("transport", providerID);
17331
17813
  }, { preconnect: () => {} });
17332
17814
  provider.options = { ...provider.options, fetch: reject };
17333
17815
  try {
17334
17816
  const runtime = configuredRuntime(config, providerID);
17335
- const baseURL = lemonadeBaseURL(runtime.baseURL ?? LEMONADE_DEFAULT_BASE_URL);
17336
- if (localRuntimeKind(runtime) !== "lemonade" || routes.some((route) => route.baseURL === undefined || lemonadeBaseURL(route.baseURL) !== baseURL))
17337
- throw new ModelResidencyError("destination");
17817
+ const kind = localRuntimeKind(runtime);
17818
+ const baseURL = normalizedBase(kind, runtimeBase(runtime));
17819
+ if (!runtimeRequiresModelResidency(kind) || routes.some((route) => route.baseURL === undefined || normalizedBase(kind, route.baseURL) !== baseURL))
17820
+ throw failure("destination", providerID);
17338
17821
  if (routes.some((route) => route.npm !== "@ai-sdk/openai-compatible") || nativeEligibleID(providerID) || !isFetch(originalFetch))
17339
- throw new ModelResidencyError("transport");
17822
+ throw failure("transport", providerID);
17340
17823
  const modelKeys = new Map;
17341
17824
  for (const [key, model] of Object.entries(provider.models ?? {})) {
17342
- if (model.options?.fetch !== undefined || model.options?.baseURL !== undefined && (typeof model.options.baseURL !== "string" || lemonadeBaseURL(model.options.baseURL) !== baseURL))
17343
- throw new ModelResidencyError("transport");
17344
- modelKeys.set(key, wireID(key, model));
17825
+ if (model.options?.fetch !== undefined || model.options?.baseURL !== undefined && (typeof model.options.baseURL !== "string" || normalizedBase(kind, model.options.baseURL) !== baseURL))
17826
+ throw failure("transport", providerID);
17827
+ modelKeys.set(key, wireID(key, model, residencyRuntimeLabel(config, providerID)));
17345
17828
  }
17346
17829
  if (!modelKeys.has(runtime.defaultModel.modelID))
17347
17830
  modelKeys.set(runtime.defaultModel.modelID, undefined);
@@ -17350,11 +17833,12 @@ function createModelResidencyGuard(config, options) {
17350
17833
  for (const [modelID2, declaredID] of modelKeys) {
17351
17834
  const id = options.resolveApiModelID === undefined ? declaredID : options.resolveApiModelID({ providerID, modelID: modelID2 });
17352
17835
  if (id !== undefined)
17353
- ids.add(wireID(id, undefined));
17836
+ ids.add(wireID(id, undefined, residencyRuntimeLabel(config, providerID)));
17354
17837
  }
17355
17838
  return ids;
17356
17839
  };
17357
- const fetch = createLemonadeGuardedFetch({
17840
+ const guardFetch = kind === "llama-swap" ? createLlamaSwapGuardedFetch : createLemonadeGuardedFetch;
17841
+ const fetch = guardFetch({
17358
17842
  ...options,
17359
17843
  baseURL,
17360
17844
  modelIDs: async () => {
@@ -17365,7 +17849,10 @@ function createModelResidencyGuard(config, options) {
17365
17849
  healthFetch: options.fetch
17366
17850
  });
17367
17851
  provider.options.fetch = fetch;
17852
+ if (kind === "llama-swap")
17853
+ provider.options.baseURL = baseURL;
17368
17854
  guarded.set(providerID, {
17855
+ kind,
17369
17856
  baseURL,
17370
17857
  fetch,
17371
17858
  originalFetch,
@@ -17379,7 +17866,7 @@ function createModelResidencyGuard(config, options) {
17379
17866
  } catch (error) {
17380
17867
  if (!(error instanceof ModelResidencyError))
17381
17868
  throw error;
17382
- (options.warn ?? console.warn)(modelResidencyMessages.providerDisabled(providerID));
17869
+ (options.warn ?? console.warn)(modelResidencyMessages.providerDisabled(providerID, residencyRuntimeLabel(config, providerID)));
17383
17870
  }
17384
17871
  }
17385
17872
  };
@@ -17389,48 +17876,52 @@ function createModelResidencyGuard(config, options) {
17389
17876
  return;
17390
17877
  const provider = guarded.get(input.model.providerID);
17391
17878
  if (provider === undefined || blocked.has(input.model.providerID) || input.provider.options.fetch !== provider.fetch || input.model.api.npm !== "@ai-sdk/openai-compatible" || nativeEligibleID(input.model.providerID))
17392
- throw new ModelResidencyError("transport");
17393
- if (lemonadeBaseURL(base) !== provider.baseURL) {
17394
- throw new ModelResidencyError("destination");
17879
+ throw failure("transport", input.model.providerID);
17880
+ if (normalizedBase(provider.kind, base) !== provider.baseURL) {
17881
+ throw failure("destination", input.model.providerID);
17395
17882
  }
17883
+ if (input.model.options.fetch !== undefined || input.model.options.baseURL !== undefined && (typeof input.model.options.baseURL !== "string" || normalizedBase(provider.kind, input.model.options.baseURL) !== provider.baseURL))
17884
+ throw failure("transport", input.model.providerID);
17396
17885
  const id = resolvedWireID({ providerID: input.model.providerID, modelID: input.model.id }, provider.modelKeys.get(input.model.id));
17397
17886
  if (id !== input.model.api.id)
17398
- throw new ModelResidencyError("identity");
17887
+ throw failure("identity", input.model.providerID);
17399
17888
  if (!provider.modelKeys.has(input.model.id))
17400
17889
  provider.modelKeys.set(input.model.id, undefined);
17401
17890
  };
17402
17891
  const localTarget = (model) => {
17403
17892
  const runtime = configuredRuntime(config, model.providerID);
17893
+ const kind = localRuntimeKind(runtime);
17894
+ const needsResidency = runtimeRequiresModelResidency(kind);
17404
17895
  if (hostConfig?.disabled_providers?.includes(model.providerID)) {
17405
- throw new ModelResidencyError("transport");
17896
+ throw failure("transport", model.providerID);
17406
17897
  }
17407
17898
  if (runtime.baseURL === undefined)
17408
- throw new ModelResidencyError("destination");
17899
+ throw failure("destination", model.providerID);
17409
17900
  const provider = hostConfig?.provider?.[model.providerID];
17410
17901
  const entry = Object.hasOwn(provider?.models ?? {}, model.modelID) ? provider?.models?.[model.modelID] : undefined;
17411
17902
  const configuredURL = provider === undefined ? undefined : providerBase(provider, entry);
17412
- if (configuredURL !== undefined && new URL(configuredURL).href.replace(/\/$/, "") !== new URL(runtime.baseURL).href.replace(/\/$/, "")) {
17413
- throw new ModelResidencyError("destination");
17903
+ if (configuredURL !== undefined && (needsResidency ? normalizedBase(kind, configuredURL) !== normalizedBase(kind, runtime.baseURL) : new URL(configuredURL).href.replace(/\/$/, "") !== new URL(runtime.baseURL).href.replace(/\/$/, ""))) {
17904
+ throw failure("destination", model.providerID);
17414
17905
  }
17415
- const modelID2 = resolvedWireID(model, entry === undefined && localRuntimeKind(runtime) === "lemonade" ? undefined : wireID(model.modelID, entry));
17906
+ const modelID2 = resolvedWireID(model, entry === undefined && needsResidency ? undefined : wireID(model.modelID, entry, residencyRuntimeLabel(config, model.providerID)));
17416
17907
  let fetch = options.fetch;
17417
- if (localRuntimeKind(runtime) === "lemonade") {
17908
+ if (needsResidency) {
17418
17909
  const installed = guarded.get(model.providerID);
17419
17910
  if (installed === undefined || blocked.has(model.providerID)) {
17420
- throw new ModelResidencyError("transport");
17911
+ throw failure("transport", model.providerID);
17421
17912
  }
17422
- if (installed.baseURL !== lemonadeBaseURL(runtime.baseURL)) {
17423
- throw new ModelResidencyError("destination");
17913
+ if (installed.baseURL !== normalizedBase(kind, runtime.baseURL)) {
17914
+ throw failure("destination", model.providerID);
17424
17915
  }
17425
17916
  if (!installed.modelKeys.has(model.modelID))
17426
17917
  installed.modelKeys.set(model.modelID, undefined);
17427
17918
  if (!installed.modelIDs().has(modelID2))
17428
- throw new ModelResidencyError("identity");
17919
+ throw failure("identity", model.providerID);
17429
17920
  fetch = installed.fetch;
17430
17921
  }
17431
17922
  const configuredHeaders = z25.record(z25.string(), z25.string()).safeParse(provider?.options?.headers ?? {});
17432
17923
  if (!configuredHeaders.success)
17433
- throw new ModelResidencyError("configuration");
17924
+ throw failure("configuration", model.providerID);
17434
17925
  const headers = new Headers(configuredHeaders.data);
17435
17926
  if (typeof provider?.options?.apiKey === "string" && !headers.has("authorization")) {
17436
17927
  headers.set("authorization", `Bearer ${provider.options.apiKey}`);
@@ -17439,7 +17930,7 @@ function createModelResidencyGuard(config, options) {
17439
17930
  headers.set(name, value);
17440
17931
  const upstream = fetch;
17441
17932
  return {
17442
- baseURL: runtime.baseURL,
17933
+ baseURL: needsResidency ? normalizedBase(kind, runtime.baseURL) : runtime.baseURL,
17443
17934
  modelID: modelID2,
17444
17935
  fetch: [...headers].length === 0 ? fetch : Object.assign(async (input, init) => {
17445
17936
  const request = new Request(input, init);
@@ -18562,7 +19053,7 @@ var memoryToolMessages = {
18562
19053
  scribeOnly: (actualRoleID) => `openteam memory: refused — memory records may only be written by the "scribe" role, not "${actualRoleID}".`,
18563
19054
  disabled: "openteam memory: semantic memory is disabled; nothing was recorded.",
18564
19055
  frontierDomainRefusal: 'openteam memory: refused — memory extraction requires local model execution, but router.executionMode is "frontier". Set router.executionMode to "local" or "mixed" and configure a reachable local runtime.',
18565
- localRuntimeUnavailable: "openteam memory: memory extraction requires a local runtime that is reachable and will not fall back to a frontier provider. Configure Ollama, LM Studio, Lemonade, or Foundry Local and run `openteam doctor`.",
19056
+ localRuntimeUnavailable: "openteam memory: memory extraction requires a local runtime that is reachable and will not fall back to a frontier provider. Configure Ollama, LM Studio, Lemonade, llama-swap, or Foundry Local and run `openteam doctor`.",
18566
19057
  localRuntimeBaseURLMissing: "openteam memory: refused — no local runtime with a base URL is configured and enabled; memory extraction is local-only.",
18567
19058
  extractionFailure: (reason) => `openteam memory: extraction failed; nothing was recorded (${reason}).`,
18568
19059
  noRecords: "openteam memory: no records were extracted; nothing was recorded.",
@@ -18632,14 +19123,16 @@ function createMemoryTool(deps) {
18632
19123
  if (runtime?.baseURL === undefined || runtime.baseURL.length === 0) {
18633
19124
  return memoryToolMessages.localRuntimeBaseURLMissing;
18634
19125
  }
18635
- const baseURL = runtime.baseURL;
19126
+ const kind = localRuntimeKind(runtime);
19127
+ const baseURL = kind === "llama-swap" ? llamaSwapBaseURL(runtime.baseURL) : runtime.baseURL;
18636
19128
  const model = configuredModel ?? runtime.defaultModel;
18637
19129
  const selectedRuntime = runtime;
18638
19130
  const deadline = {
18639
19131
  ...deps.residencyOptions,
19132
+ ...kind === "llama-swap" ? { runtimeLabel: "llama-swap" } : {},
18640
19133
  ...signal === undefined ? {} : { signal }
18641
19134
  };
18642
- const reachable = localRuntimeKind(runtime) === "lemonade" ? await withResidencyDeadline((signal2) => deps.localRuntimeReachable(selectedRuntime, signal2), deadline) : await deps.localRuntimeReachable(runtime);
19135
+ const reachable = runtimeRequiresModelResidency(kind) ? await withResidencyDeadline((signal2) => deps.localRuntimeReachable(selectedRuntime, signal2), deadline) : await deps.localRuntimeReachable(runtime);
18643
19136
  throwIfAborted();
18644
19137
  if (!reachable)
18645
19138
  return memoryToolMessages.localRuntimeUnavailable;
@@ -18647,7 +19140,7 @@ function createMemoryTool(deps) {
18647
19140
  const target = resolveTarget ? await withResidencyDeadline(async () => resolveTarget(model), deadline) : {
18648
19141
  baseURL,
18649
19142
  modelID: model.modelID,
18650
- fetch: localRuntimeKind(runtime) === "lemonade" ? createLemonadeGuardedFetch({
19143
+ fetch: runtimeRequiresModelResidency(kind) ? (kind === "llama-swap" ? createLlamaSwapGuardedFetch : createLemonadeGuardedFetch)({
18651
19144
  baseURL,
18652
19145
  fetch: deps.fetch,
18653
19146
  modelIDs: new Set([model.modelID]),
@@ -21566,6 +22059,13 @@ function createLocalEmbedder(request, deps) {
21566
22059
  return (texts) => embedTexts({ ...request, texts }, deps);
21567
22060
  }
21568
22061
 
22062
+ // src/messages/memoryRuntime.ts
22063
+ var memoryRuntimeMessages = {
22064
+ providerUnavailable: "[openteam] Semantic memory requires exactly one enabled local runtime matching the configured embeddings provider. No embedding request was sent.",
22065
+ unavailable: "[openteam] Semantic memory initialization failed; recall and memory injection are unavailable.",
22066
+ rejectedLines: (count) => `[openteam] memory log rejected ${count} line(s); semantic memory may be incomplete.`
22067
+ };
22068
+
21569
22069
  // src/storage/index/bunSqlite.ts
21570
22070
  async function createBunSqliteDatabase(path4) {
21571
22071
  const mod = await import("bun:sqlite");
@@ -21649,7 +22149,14 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
21649
22149
  if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
21650
22150
  return;
21651
22151
  }
21652
- const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
22152
+ const warn = deps.warn ?? console.warn;
22153
+ const configuredModel = semantic.embeddings.model;
22154
+ const candidates = configuredModel == null ? config.local.runtimes.filter((rt) => rt.enabled && rt.baseURL !== undefined) : config.local.runtimes.filter((rt) => rt.defaultModel.providerID === configuredModel.providerID);
22155
+ const runtime = candidates[0];
22156
+ if (configuredModel != null && (candidates.length !== 1 || runtime?.enabled !== true)) {
22157
+ warn(memoryRuntimeMessages.providerUnavailable);
22158
+ return;
22159
+ }
21653
22160
  if (runtime?.baseURL === undefined) {
21654
22161
  return;
21655
22162
  }
@@ -21658,15 +22165,22 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
21658
22165
  const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
21659
22166
  const fetchImpl = deps.fetch ?? globalThis.fetch;
21660
22167
  const now = deps.now ?? (() => Date.now());
21661
- const warn = deps.warn ?? console.warn;
22168
+ const kind = localRuntimeKind(runtime);
21662
22169
  try {
22170
+ const baseURL = kind === "llama-swap" ? llamaSwapBaseURL(runtime.baseURL) : runtime.baseURL;
22171
+ const guardedFetch = runtimeRequiresModelResidency(kind) ? (kind === "llama-swap" ? createLlamaSwapGuardedFetch : createLemonadeGuardedFetch)({
22172
+ baseURL,
22173
+ fetch: fetchImpl,
22174
+ healthFetch: fetchImpl,
22175
+ modelIDs: new Set([modelID2])
22176
+ }) : fetchImpl;
21663
22177
  const db = await openDatabase(semantic.indexPath);
21664
22178
  const index = createMemoryIndex(db, warn);
21665
22179
  const rebuildResult = await rebuildMemoryIndexFromStorage(index, semantic.logPath, { storage: deps.storage });
21666
22180
  if (rebuildResult.rejectedLines > 0) {
21667
- warn(`[openteam] memory log rejected ${rebuildResult.rejectedLines} line(s); semantic memory may be incomplete.`);
22181
+ warn(memoryRuntimeMessages.rejectedLines(rebuildResult.rejectedLines));
21668
22182
  }
21669
- const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID: modelID2 }, { fetch: fetchImpl });
22183
+ const embedder = makeEmbedder({ baseURL, modelID: modelID2 }, { fetch: guardedFetch });
21670
22184
  await ensureMemoryEmbeddings({ index, embedder, model: modelID2, warn });
21671
22185
  return {
21672
22186
  index,
@@ -21676,6 +22190,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
21676
22190
  semantic
21677
22191
  };
21678
22192
  } catch {
22193
+ warn(memoryRuntimeMessages.unavailable);
21679
22194
  return;
21680
22195
  }
21681
22196
  }
@@ -22046,8 +22561,8 @@ function readRun(runDir, runID) {
22046
22561
  if (metadataText === undefined || eventsText === undefined) {
22047
22562
  throw new LifecycleFsError("corrupt");
22048
22563
  }
22049
- const metadata2 = parseJson(metadataText, parseLifecycleRunMetadata);
22050
- if (metadata2.runID !== runID) {
22564
+ const metadata3 = parseJson(metadataText, parseLifecycleRunMetadata);
22565
+ if (metadata3.runID !== runID) {
22051
22566
  throw new LifecycleFsError("corrupt");
22052
22567
  }
22053
22568
  let decoded;
@@ -22062,10 +22577,10 @@ function readRun(runDir, runID) {
22062
22577
  }
22063
22578
  }
22064
22579
  const first = decoded.events[0];
22065
- if (first === undefined || first.type !== "run.started" || first.source !== metadata2.source || first.executionAuthority !== metadata2.executionAuthority || first.root.operationID !== metadata2.root.operationID || first.root.taskID !== metadata2.root.taskID) {
22580
+ if (first === undefined || first.type !== "run.started" || first.source !== metadata3.source || first.executionAuthority !== metadata3.executionAuthority || first.root.operationID !== metadata3.root.operationID || first.root.taskID !== metadata3.root.taskID) {
22066
22581
  throw new LifecycleFsError("corrupt");
22067
22582
  }
22068
- return { metadata: metadata2, decoded };
22583
+ return { metadata: metadata3, decoded };
22069
22584
  }
22070
22585
  function terminalOutcome(events) {
22071
22586
  const last = events.at(-1);
@@ -22143,7 +22658,7 @@ var createFsLifecycleJournal = (options) => {
22143
22658
  mkdirSync3(stagingPath);
22144
22659
  const writerToken = issueWriterToken();
22145
22660
  const eventAt = now();
22146
- const metadata2 = parseLifecycleRunMetadata({
22661
+ const metadata3 = parseLifecycleRunMetadata({
22147
22662
  ...parsedInput,
22148
22663
  version: 1,
22149
22664
  createdAt: eventAt
@@ -22159,7 +22674,7 @@ var createFsLifecycleJournal = (options) => {
22159
22674
  executionAuthority: parsedInput.executionAuthority,
22160
22675
  root: parsedInput.root
22161
22676
  });
22162
- writeDurable2(join15(stagingPath, METADATA_FILE), `${JSON.stringify(metadata2)}
22677
+ writeDurable2(join15(stagingPath, METADATA_FILE), `${JSON.stringify(metadata3)}
22163
22678
  `);
22164
22679
  writeDurable2(join15(stagingPath, EVENTS_FILE), encodeLifecycleJournal([event]));
22165
22680
  writeDurable2(join15(stagingPath, OWNER_FILE2), `${JSON.stringify(lease)}
@@ -22175,7 +22690,7 @@ var createFsLifecycleJournal = (options) => {
22175
22690
  stagingPath = undefined;
22176
22691
  return ok({
22177
22692
  handle: { runID, writerToken },
22178
- metadata: metadata2,
22693
+ metadata: metadata3,
22179
22694
  event,
22180
22695
  lease,
22181
22696
  head: 1
@@ -23296,6 +23811,20 @@ var server = async (ctx, rawOptions) => {
23296
23811
  storage,
23297
23812
  now,
23298
23813
  localRuntimeReachable: async (runtime, signal) => {
23814
+ if (localRuntimeKind(runtime) === "llama-swap") {
23815
+ if (ctx.client.config?.providers !== undefined)
23816
+ await ensureAvailability();
23817
+ signal?.throwIfAborted();
23818
+ const target = modelResidency.localTarget(resolveMemorySemantic(config).extraction.model ?? runtime.defaultModel);
23819
+ const residency = await readLlamaSwapResidency(target.baseURL, globalThis.fetch, {
23820
+ ...signal === undefined ? {} : { signal }
23821
+ });
23822
+ if (!residency.has(target.modelID))
23823
+ throw new ModelResidencyError("identity", "llama-swap");
23824
+ if (residency.get(target.modelID)?.loaded !== true)
23825
+ throw new ModelResidencyError("inactive", "llama-swap");
23826
+ return true;
23827
+ }
23299
23828
  if (localRuntimeKind(runtime) === "lemonade") {
23300
23829
  if (ctx.client.config?.providers !== undefined)
23301
23830
  await ensureAvailability();