@coder/ai-sdk-agent 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -222,7 +222,7 @@ conversation with server‑side history). `agent.chatId` is the current chat id.
222
222
  - `agent.resetSession()` — start a fresh chat on the next turn (reuse one instance for sequential turns; you don't need a new agent per turn).
223
223
  - `agent.interrupt({ signal? })` — interrupt an in‑flight generation.
224
224
  - `agent.archive({ signal? })` — archive the underlying chat (cleanup; see [Cleanup](#cleanup)).
225
- - `agent.listModels()` — list the deployment's model configs, so you don't have to guess the `model` hint.
225
+ - `agent.listModels()` — list the organization's model configs, so you don't have to guess the `model` hint.
226
226
  - Resume a prior chat: `new CoderAgent({ …, chatId: "…" })` — optionally with
227
227
  `lastSeenMessageId` (the persisted resume cursor, read from
228
228
  `agent.lastSeenMessageId`) to skip the resumed turn's pre‑prompt history
@@ -905,13 +905,17 @@ sequence pinpoints _where_ a turn died:
905
905
  | `chatId` | resume an existing chat |
906
906
  | `lastSeenMessageId` | resume cursor for `chatId` (persist `agent.lastSeenMessageId`) — skips the resumed turn's pre‑prompt history probe |
907
907
 
908
- The `model` hint resolves against the deployment's model configs in order: a
909
- config UUID is used as‑is, then an exact `provider:model` match, an exact model
910
- id, a display‑name substring (case‑insensitive), and finally a model‑id
911
- substring. Partial payloads from older/newer servers are tolerated (entries
912
- match on the fields they carry), and an unresolvable hint falls back to the
913
- server's default model instead of failing. Use `agent.listModels()` to see
914
- what's available.
908
+ The `model` hint resolves against the organization's model configs
909
+ (`GET /api/v2/organizations/{organizationId}/chats/models`, with the provider
910
+ type joined from the response's provider descriptors) in order: a config UUID
911
+ is used as‑is, then an exact `provider:model` match, an exact model id, a
912
+ display‑name substring (case‑insensitive), and finally a model‑id substring.
913
+ On older deployments where the organization‑scoped route does not exist yet
914
+ (404), resolution falls back once to the legacy deployment‑wide
915
+ `/model-configs` listing. Partial payloads from older/newer servers are
916
+ tolerated (entries match on the fields they carry), and an unresolvable hint
917
+ falls back to the server's default model instead of failing. Use
918
+ `agent.listModels()` to see what's available.
915
919
 
916
920
  ## How it works
917
921
 
package/dist/index.d.ts CHANGED
@@ -245,10 +245,14 @@ interface UploadChatFileResponse {
245
245
  id: string;
246
246
  }
247
247
  /**
248
- * A configured model (`GET /api/experimental/chats/model-configs`). Depending
249
- * on the server version and how a config was created, `provider`, `model`, and
250
- * `display_name` may be absent on individual entries, so treat them as
251
- * optional and guard reads.
248
+ * A configured model, normalized to the shape of the legacy
249
+ * `GET /api/experimental/chats/model-configs` listing (removed upstream in
250
+ * coder/coder#28632). The client synthesizes this from the organization-scoped
251
+ * {@link OrganizationChatModelsResponse} by joining each model's
252
+ * `ai_provider_id` to its provider descriptor's `type` (→ `provider`).
253
+ * Depending on the server version and how a config was created, `provider`,
254
+ * `model`, and `display_name` may be absent on individual entries, so treat
255
+ * them as optional and guard reads.
252
256
  */
253
257
  interface ChatModelConfig {
254
258
  id: string;
@@ -261,6 +265,59 @@ interface ChatModelConfig {
261
265
  context_limit?: number;
262
266
  compression_threshold?: number;
263
267
  }
268
+ /**
269
+ * An organization-scoped chat model, as returned by
270
+ * `GET /api/v2/organizations/{organization}/chats/models` (codersdk
271
+ * `ChatModel`). The provider type string is not carried here — join
272
+ * `ai_provider_id` against {@link ChatModelProviderDescriptor.id}. Fields
273
+ * other than `id` are treated as optional to tolerate partial payloads from
274
+ * older/newer servers.
275
+ */
276
+ interface ChatModel {
277
+ id: string;
278
+ organization_id?: string;
279
+ /** FK into the response's `providers[].id`. */
280
+ ai_provider_id?: string;
281
+ model?: string;
282
+ display_name?: string;
283
+ enabled?: boolean;
284
+ is_default?: boolean;
285
+ context_limit?: number;
286
+ compression_threshold?: number;
287
+ reasoning_efforts?: string[];
288
+ created_at?: string;
289
+ updated_at?: string;
290
+ }
291
+ /**
292
+ * The redacted view of an AI provider carried on the organization-scoped
293
+ * model collection (codersdk `ChatModelProviderDescriptor`) — capability
294
+ * metadata only, no key material or base URLs. `type` is the provider type
295
+ * string (e.g. `anthropic`) that the legacy listing carried per-model.
296
+ */
297
+ interface ChatModelProviderDescriptor {
298
+ id: string;
299
+ type?: string;
300
+ display_name?: string;
301
+ icon?: string;
302
+ enabled?: boolean;
303
+ available?: boolean;
304
+ unavailable_reason?: string;
305
+ }
306
+ /** A configured provider type the server build does not support. */
307
+ interface ChatUnsupportedProvider {
308
+ provider?: string;
309
+ display_name?: string;
310
+ }
311
+ /**
312
+ * Response of `GET /api/v2/organizations/{organization}/chats/models`
313
+ * (codersdk `OrganizationChatModelsResponse`): the org's model configs plus
314
+ * the provider descriptors they reference.
315
+ */
316
+ interface OrganizationChatModelsResponse {
317
+ models?: ChatModel[];
318
+ providers?: ChatModelProviderDescriptor[];
319
+ unsupported_providers?: ChatUnsupportedProvider[];
320
+ }
264
321
  type ChatStreamEventType = "message_part" | "message" | "status" | "error" | "queue_update" | "retry" | "action_required" | "preview_reset" | "history_reset";
265
322
  interface ChatStreamMessagePart {
266
323
  role?: ChatMessageRole;
@@ -698,6 +755,18 @@ declare class CoderChatClient {
698
755
  #private;
699
756
  readonly baseUrl: string;
700
757
  constructor(options: CoderChatClientOptions);
758
+ /**
759
+ * List the organization's model configs, normalized to
760
+ * {@link ChatModelConfig} (the legacy per-model `provider` string is joined
761
+ * from the response's provider descriptors). See {@link #fetchModelConfigs}
762
+ * for the endpoint and version-skew fallback.
763
+ */
764
+ listModelConfigs(organizationId: string, signal?: AbortSignal): Promise<ChatModelConfig[]>;
765
+ /**
766
+ * @deprecated The deployment-scoped `GET /api/experimental/chats/model-configs`
767
+ * route this overload calls was removed upstream (coder/coder#28632) and
768
+ * returns 404 on current deployments — pass an `organizationId`.
769
+ */
701
770
  listModelConfigs(signal?: AbortSignal): Promise<ChatModelConfig[]>;
702
771
  createChat(req: CreateChatRequest, signal?: AbortSignal): Promise<Chat>;
703
772
  getChat(chatId: string, signal?: AbortSignal): Promise<Chat>;
@@ -783,6 +852,12 @@ declare class CoderChatClient {
783
852
  * fields they do carry — an entry without `provider` still matches by its
784
853
  * model id, one without `model` only by display name.
785
854
  */
855
+ resolveModelConfigId(hint: string, organizationId: string, signal?: AbortSignal): Promise<string | undefined>;
856
+ /**
857
+ * @deprecated The deployment-scoped `GET /api/experimental/chats/model-configs`
858
+ * route this overload calls was removed upstream (coder/coder#28632) and
859
+ * returns 404 on current deployments — pass an `organizationId`.
860
+ */
786
861
  resolveModelConfigId(hint: string, signal?: AbortSignal): Promise<string | undefined>;
787
862
  }
788
863
 
@@ -1079,9 +1154,9 @@ declare class CoderAgent<TOOLS extends ToolSet = {}> implements Agent<never, TOO
1079
1154
  generate(options: Parameters<InnerAgent<TOOLS>["generate"]>[0]): ReturnType<InnerAgent<TOOLS>["generate"]>;
1080
1155
  stream(options: Parameters<InnerAgent<TOOLS>["stream"]>[0]): ReturnType<InnerAgent<TOOLS>["stream"]>;
1081
1156
  /**
1082
- * List the model configs available on the deployment. Use this to discover
1083
- * valid values for the `model` hint instead of guessing ids — match on
1084
- * `id`, `provider`/`model`, or `display_name`.
1157
+ * List the model configs available to this agent's organization. Use this
1158
+ * to discover valid values for the `model` hint instead of guessing ids —
1159
+ * match on `id`, `provider`/`model`, or `display_name`.
1085
1160
  */
1086
1161
  listModels(signal?: AbortSignal): Promise<ChatModelConfig[]>;
1087
1162
  /**
@@ -1628,4 +1703,4 @@ declare class CoderChatError extends CoderAgentError {
1628
1703
  constructor(payload: ChatErrorPayload);
1629
1704
  }
1630
1705
 
1631
- export { CHAT_ATTACHMENT_MEDIA_TYPES, CODER_PROVIDER_OPTIONS, type Chat, type ChatAttachment, type ChatAttachmentMediaType, type ChatBusyBehavior, type ChatClientType, type ChatErrorPayload, type ChatFileInput, type ChatInputPart, type ChatInputPartType, type ChatMessage, type ChatMessagePart, type ChatMessagePartType, type ChatMessageRole, type ChatMessageUsage, type ChatMessagesResponse, type ChatMessagesToUIMessagesOptions, type ChatModelConfig, type ChatPlanMode, type ChatQueuedMessage, type ChatStatus, type ChatStreamActionRequired, type ChatStreamEvent, type ChatStreamEventType, type ChatStreamMessagePart, type ChatStreamRetry, type ChatStreamStatus, type ChatStreamToolCall, type ChatWatchEvent, type ChatWatchEventKind, CoderAgent, CoderAgentError, type CoderAgentSettings, CoderApiError, type CoderArchiveResult, CoderChatClient, type CoderChatClientOptions, CoderChatError, type CoderClientOperation, type CoderFileProviderOptions, type CoderInterruptResult, CoderLanguageModel, type CoderLanguageModelConfig, CoderStreamError, type CoderTransportEvent, type CreateChatMessageRequest, type CreateChatMessageResponse, type CreateChatRequest, type DynamicTool, type FileContent, type FilePartUploader, type HttpErrorTransportEvent, type HttpRequestTransportEvent, type HttpResponseTransportEvent, MAX_CHAT_FILE_SIZE_BYTES, type PreviewShareLevel, type ResolvedFile, type SegmentSettleTransportEvent, type SegmentStartTransportEvent, type SharePreviewOptions, type SharedWorkspacePreview, type StreamCloseTransportEvent, type StreamDialTransportEvent, type StreamErrorTransportEvent, type StreamEventTransportEvent, type StreamOpenTransportEvent, type StreamRedialTransportEvent, type SubmitToolResultsRequest, TERMINAL_STATUSES, type ToolResult, type TransportEventHandler, type TurnAction, TurnTranslator, type TurnTranslatorOptions, type UpdateChatRequest, type UploadChatFileResponse, type UploadedChatFile, type UserContent, type WatchChatEventsOptions, type WebSocketFactory, type WebSocketLike, type WorkspaceFileStore, type WorkspacePlacement, type WorkspacePreview, type WorkspacePreviewOptions, chatMessagesToUIMessages, classifyTurnAction, dataContentToFileContent, dynamicToolNames, extractSystemPrompt, resolveFileContent, streamChatEvents, toolsToDynamicTools, userContentToInputParts, watchChatEvents };
1706
+ export { CHAT_ATTACHMENT_MEDIA_TYPES, CODER_PROVIDER_OPTIONS, type Chat, type ChatAttachment, type ChatAttachmentMediaType, type ChatBusyBehavior, type ChatClientType, type ChatErrorPayload, type ChatFileInput, type ChatInputPart, type ChatInputPartType, type ChatMessage, type ChatMessagePart, type ChatMessagePartType, type ChatMessageRole, type ChatMessageUsage, type ChatMessagesResponse, type ChatMessagesToUIMessagesOptions, type ChatModel, type ChatModelConfig, type ChatModelProviderDescriptor, type ChatPlanMode, type ChatQueuedMessage, type ChatStatus, type ChatStreamActionRequired, type ChatStreamEvent, type ChatStreamEventType, type ChatStreamMessagePart, type ChatStreamRetry, type ChatStreamStatus, type ChatStreamToolCall, type ChatUnsupportedProvider, type ChatWatchEvent, type ChatWatchEventKind, CoderAgent, CoderAgentError, type CoderAgentSettings, CoderApiError, type CoderArchiveResult, CoderChatClient, type CoderChatClientOptions, CoderChatError, type CoderClientOperation, type CoderFileProviderOptions, type CoderInterruptResult, CoderLanguageModel, type CoderLanguageModelConfig, CoderStreamError, type CoderTransportEvent, type CreateChatMessageRequest, type CreateChatMessageResponse, type CreateChatRequest, type DynamicTool, type FileContent, type FilePartUploader, type HttpErrorTransportEvent, type HttpRequestTransportEvent, type HttpResponseTransportEvent, MAX_CHAT_FILE_SIZE_BYTES, type OrganizationChatModelsResponse, type PreviewShareLevel, type ResolvedFile, type SegmentSettleTransportEvent, type SegmentStartTransportEvent, type SharePreviewOptions, type SharedWorkspacePreview, type StreamCloseTransportEvent, type StreamDialTransportEvent, type StreamErrorTransportEvent, type StreamEventTransportEvent, type StreamOpenTransportEvent, type StreamRedialTransportEvent, type SubmitToolResultsRequest, TERMINAL_STATUSES, type ToolResult, type TransportEventHandler, type TurnAction, TurnTranslator, type TurnTranslatorOptions, type UpdateChatRequest, type UploadChatFileResponse, type UploadedChatFile, type UserContent, type WatchChatEventsOptions, type WebSocketFactory, type WebSocketLike, type WorkspaceFileStore, type WorkspacePlacement, type WorkspacePreview, type WorkspacePreviewOptions, chatMessagesToUIMessages, classifyTurnAction, dataContentToFileContent, dynamicToolNames, extractSystemPrompt, resolveFileContent, streamChatEvents, toolsToDynamicTools, userContentToInputParts, watchChatEvents };
package/dist/index.js CHANGED
@@ -752,15 +752,10 @@ var CoderChatClient = class {
752
752
  });
753
753
  return await this.#json(res);
754
754
  }
755
- // --- REST -----------------------------------------------------------------
756
- listModelConfigs(signal) {
757
- return this.#request(
758
- "listModelConfigs",
759
- "GET",
760
- `${API_PREFIX}/model-configs`,
761
- void 0,
762
- signal
763
- );
755
+ listModelConfigs(organizationIdOrSignal, maybeSignal) {
756
+ const organizationId = typeof organizationIdOrSignal === "string" ? organizationIdOrSignal : void 0;
757
+ const signal = typeof organizationIdOrSignal === "string" ? maybeSignal : organizationIdOrSignal;
758
+ return this.#fetchModelConfigs("listModelConfigs", organizationId, signal);
764
759
  }
765
760
  createChat(req, signal) {
766
761
  return this.#request("createChat", "POST", API_PREFIX, req, signal);
@@ -948,32 +943,70 @@ var CoderChatClient = class {
948
943
  }
949
944
  // --- Helpers --------------------------------------------------------------
950
945
  /**
951
- * Resolves a user-friendly model hint to a model-config UUID.
946
+ * Fetch the model listing for hint resolution / listing, tolerating version
947
+ * skew in both directions:
948
+ *
949
+ * - Primary: `GET /api/v2/organizations/{organizationId}/chats/models` —
950
+ * the organization-scoped route that replaced the legacy listing upstream
951
+ * (coder/coder#28632); its response is normalized to the legacy
952
+ * {@link ChatModelConfig} array by {@link joinOrgModels}.
953
+ * - Newer SDK, older deployment: the org-scoped route does not exist yet,
954
+ * so on a 404 this falls back once to the legacy
955
+ * `GET /api/experimental/chats/model-configs`, whose body already is the
956
+ * legacy array (parsed tolerantly: a non-array body yields no configs and
957
+ * non-object entries are dropped). (Older SDK, newer deployment is the
958
+ * reverse skew: the legacy route is a reserved 404 upstream, which is why
959
+ * the org-scoped route is primary.)
960
+ * - The deprecated no-`organizationId` overloads call the legacy route
961
+ * directly, preserving their historical behavior.
952
962
  *
953
- * Accepts: a config UUID (returned as-is), a `provider:model` id
954
- * (e.g. `anthropic:claude-haiku-4-5-20251001`), a bare model id, or a
955
- * display-name substring (case-insensitive). Returns `undefined` if no
956
- * match is found, in which case the caller should let chatd pick the default.
963
+ * When both routes 404, the org-scoped error is rethrown — it names the
964
+ * organization actually queried (e.g. a mistyped `organizationId`), while
965
+ * the legacy route's generic "Route not found" would mask it.
957
966
  *
958
- * Tolerates partial payloads from older/newer servers: a malformed listing
959
- * (empty body, non-array JSON, null entries) resolves as no match, and
960
- * entries missing `provider`, `model`, or `display_name` are matched on the
961
- * fields they do carry — an entry without `provider` still matches by its
962
- * model id, one without `model` only by display name.
967
+ * Both requests stamp the calling public method's `op` on the exchange's
968
+ * `http:*` events — the stamp names the caller's intent, not the wire shape.
963
969
  */
964
- async resolveModelConfigId(hint, signal) {
970
+ async #fetchModelConfigs(op, organizationId, signal) {
971
+ const legacy = async () => {
972
+ const raw = await this.#request(
973
+ op,
974
+ "GET",
975
+ `${API_PREFIX}/model-configs`,
976
+ void 0,
977
+ signal
978
+ );
979
+ return (Array.isArray(raw) ? raw : []).filter(
980
+ (c) => typeof c === "object" && c !== null
981
+ );
982
+ };
983
+ if (organizationId === void 0) return legacy();
984
+ let res;
985
+ try {
986
+ res = await this.#request(
987
+ op,
988
+ "GET",
989
+ `/api/v2/organizations/${encodeURIComponent(organizationId)}/chats/models`,
990
+ void 0,
991
+ signal
992
+ );
993
+ } catch (err) {
994
+ if (!(err instanceof CoderApiError) || err.status !== 404) throw err;
995
+ try {
996
+ return await legacy();
997
+ } catch (legacyErr) {
998
+ if (legacyErr instanceof CoderApiError && legacyErr.status === 404) throw err;
999
+ throw legacyErr;
1000
+ }
1001
+ }
1002
+ return joinOrgModels(res);
1003
+ }
1004
+ async resolveModelConfigId(hint, organizationIdOrSignal, maybeSignal) {
1005
+ const organizationId = typeof organizationIdOrSignal === "string" ? organizationIdOrSignal : void 0;
1006
+ const signal = typeof organizationIdOrSignal === "string" ? maybeSignal : organizationIdOrSignal;
965
1007
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
966
1008
  if (UUID_RE.test(hint)) return hint;
967
- const raw = await this.#request(
968
- "resolveModelConfigId",
969
- "GET",
970
- `${API_PREFIX}/model-configs`,
971
- void 0,
972
- signal
973
- );
974
- const configs = (Array.isArray(raw) ? raw : []).filter(
975
- (c) => typeof c === "object" && c !== null
976
- );
1009
+ const configs = await this.#fetchModelConfigs("resolveModelConfigId", organizationId, signal);
977
1010
  const lower = hint.toLowerCase();
978
1011
  const colon = hint.indexOf(":");
979
1012
  const provider = colon >= 0 ? hint.slice(0, colon).toLowerCase() : void 0;
@@ -995,6 +1028,25 @@ var CoderChatClient = class {
995
1028
  return byModelSubstring?.id;
996
1029
  }
997
1030
  };
1031
+ function joinOrgModels(res) {
1032
+ const body = typeof res === "object" && res !== null ? res : {};
1033
+ const models = Array.isArray(body.models) ? body.models : [];
1034
+ const providers = Array.isArray(body.providers) ? body.providers : [];
1035
+ const providerTypeById = /* @__PURE__ */ new Map();
1036
+ for (const p of providers) {
1037
+ if (typeof p !== "object" || p === null) continue;
1038
+ if (typeof p.id === "string" && typeof p.type === "string") providerTypeById.set(p.id, p.type);
1039
+ }
1040
+ const configs = [];
1041
+ for (const m of models) {
1042
+ if (typeof m !== "object" || m === null) continue;
1043
+ const config = { ...m };
1044
+ const providerType = typeof m.ai_provider_id === "string" ? providerTypeById.get(m.ai_provider_id) : void 0;
1045
+ if (providerType !== void 0) config.provider = providerType;
1046
+ configs.push(config);
1047
+ }
1048
+ return configs;
1049
+ }
998
1050
 
999
1051
  // src/coder/workspaces.ts
1000
1052
  async function request(conn, method, path, body, signal) {
@@ -2160,6 +2212,7 @@ var CoderLanguageModel = class {
2160
2212
  if (this.#config.model) {
2161
2213
  this.#resolvedModelConfigId = await this.#config.client.resolveModelConfigId(
2162
2214
  this.#config.model,
2215
+ this.#config.organizationId,
2163
2216
  signal
2164
2217
  );
2165
2218
  }
@@ -2923,12 +2976,12 @@ var CoderAgent = class {
2923
2976
  }
2924
2977
  // --- session helpers ------------------------------------------------------
2925
2978
  /**
2926
- * List the model configs available on the deployment. Use this to discover
2927
- * valid values for the `model` hint instead of guessing ids — match on
2928
- * `id`, `provider`/`model`, or `display_name`.
2979
+ * List the model configs available to this agent's organization. Use this
2980
+ * to discover valid values for the `model` hint instead of guessing ids —
2981
+ * match on `id`, `provider`/`model`, or `display_name`.
2929
2982
  */
2930
2983
  listModels(signal) {
2931
- return this.#client.listModelConfigs(signal);
2984
+ return this.#client.listModelConfigs(this.#organizationId, signal);
2932
2985
  }
2933
2986
  /**
2934
2987
  * Start a fresh chatd chat on the next turn. The dropped chat still exists