@meistrari/chat-nuxt 4.5.0-rc.2 → 4.5.0-rc.3

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 (33) hide show
  1. package/README.md +13 -2
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/embed/components/chat-embed-inner.vue +2 -5
  4. package/dist/runtime/embed/components/chat-embed.vue +1 -0
  5. package/dist/runtime/embed/composables/harness-adapter.d.ts +0 -1
  6. package/dist/runtime/embed/types.d.ts +3 -4
  7. package/dist/runtime/lightweight/embed/composables/model-selection.d.ts +1 -2
  8. package/dist/runtime/lightweight/embed/composables/model-selection.js +5 -22
  9. package/dist/runtime/lightweight/messages/composables/chat-model-catalog.d.ts +4 -6
  10. package/dist/runtime/lightweight/messages/composables/chat-model-catalog.js +5 -52
  11. package/dist/runtime/lightweight/messages/model-options.d.ts +4 -6
  12. package/dist/runtime/lightweight/messages/model-options.js +3 -10
  13. package/dist/runtime/messages/components/chat-model-selector.vue +31 -20
  14. package/dist/runtime/messages/composables/chat-model-catalog.d.ts +8 -0
  15. package/dist/runtime/messages/composables/chat-model-catalog.js +9 -0
  16. package/dist/runtime/messages/composables/chat.js +1 -3
  17. package/dist/runtime/messages/composables/model-catalog-cache.d.ts +7 -0
  18. package/dist/runtime/messages/composables/model-catalog-cache.js +81 -0
  19. package/dist/runtime/messages/model-availability.d.ts +2 -5
  20. package/dist/runtime/messages/model-availability.js +6 -29
  21. package/dist/runtime/shared/composables/chat-api.d.ts +1 -0
  22. package/dist/runtime/shared/composables/chat-api.js +2 -0
  23. package/dist/runtime/shared/composables/chat-request-context.d.ts +2 -0
  24. package/dist/runtime/shared/composables/chat-request-context.js +4 -0
  25. package/dist/runtime/types/schemas/chat/conversations.d.ts +2 -22
  26. package/dist/runtime/types/schemas/chat/model-catalog.d.ts +28 -0
  27. package/dist/runtime/types/schemas/chat/model-catalog.js +30 -0
  28. package/dist/runtime/types/schemas/chat/models.d.ts +3 -12
  29. package/dist/runtime/types/schemas/chat/models.js +4 -1
  30. package/dist/runtime/types/schemas/index.d.ts +1 -0
  31. package/dist/runtime/types/schemas/index.js +1 -0
  32. package/dist/runtime/types/schemas/integrations/tela-agent-api.d.ts +1 -11
  33. package/package.json +1 -1
package/README.md CHANGED
@@ -75,6 +75,17 @@ The table below is a quick index for reading in-repo. **When you change a prop,
75
75
 
76
76
  The embed automatically sets the browser tab title to the active conversation title and synchronizes its ID to `?conversationId=` using router replacement, preserving other query parameters and the URL fragment. Starting a new conversation removes the conversation parameter. Opening or reloading that URL restores the conversation; existing `?conversation=` links are also accepted and converted to `?conversationId=`. Explicit `conversationId` and `initialConversationId` props take precedence over the URL on mount. Without a conversation title, the host application controls the tab title.
77
77
 
78
+ The model catalog loads in the background and never gates send, retry or model
79
+ persistence. Its in-memory cache is scoped to workspace, agent, branch,
80
+ conversation scope, conversation and harness. Lists remain fresh for five minutes;
81
+ refresh failures retain the last valid list for up to 24 hours. Browser requests have a 30-second deadline including proxying and harness resolution;
82
+ the backend catalog lookup has a three-second timeout. Requests share one pending
83
+ request per key and back off from 30 seconds
84
+ to five minutes on failure. Mounted consumers periodically refresh the cache.
85
+ Reloading the page starts a new cache. Cold Claude pickers use bundled options;
86
+ Lightweight keeps the configured/restored selection or the agent default. Neither
87
+ harness changes the selected model when catalog metadata arrives.
88
+
78
89
  ### `<MeistrariChatEmbed>` props
79
90
 
80
91
  Declared in `src/runtime/embed/types.ts`; prop, feature-flag, and event payload types are importable from the package root or `@meistrari/chat-nuxt/types/embed`.
@@ -88,8 +99,8 @@ Declared in `src/runtime/embed/types.ts`; prop, feature-flag, and event payload
88
99
  | `initialConversationId` | `string \| null` | `null` | Conversation to open once on mount when `conversationId` is uncontrolled |
89
100
  | `conversationScope` | `string \| null` | `null` | Isolate conversation history by technical scope within the current workspace and runtime. Scoped embeds only see same-scope conversations; values are trimmed, blank becomes `null`, and the server accepts 1-200 chars from `A-Z`, `a-z`, `0-9`, `.`, `_`, `:`, `/`, `-` |
90
101
  | `defaultConversationCreatorFilter` | `'all' \| 'mine'` | `'all'` | Initial sidebar creator filter: every conversation in scope, or only the current user's |
91
- | `availableModels` | `AvailableChatModels` | Workspace catalog | Optional ordered subset of models for the active workspace. With the served catalog (lightweight harness), ids the catalog does not offer as available are dropped, and the full catalog is used when none remain; otherwise unknown ids fail at runtime. A restored unavailable selection stays visible and blocked until the user chooses an available model |
92
- | `defaultModel` | `ChatModel` | Package default | Initial model for untouched drafts. Explicit selections and existing conversations take precedence; `availableModels` still limits the selection |
102
+ | `availableModels` | `AvailableChatModels` | Harness catalog | Visual filter and ordering for catalog options. Unknown ids are omitted; if no ids match, the full catalog is shown. Does not change defaults, restored selections or execution. The Agent validates availability |
103
+ | `defaultModel` | `ChatModel` | Package default | Initial model for untouched drafts. Explicit selections and existing conversations take precedence; `availableModels` only filters the selector |
93
104
  | `defaultReasoningEffort` | `ChatReasoningEffort` | Automatic (`null`) | Initial reasoning effort for untouched drafts. Explicit user choices (including automatic) take precedence |
94
105
  | `conversationCreatorFilter` | `'all' \| 'mine'` | — | Controlled sidebar creator filter; use with `v-model:conversation-creator-filter` when the host changes it at runtime |
95
106
  | `hideConversationCreatorFilter` | `boolean` | `false` | Hide the creator selector without changing the active filter |
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
3
  "configKey": "chatNuxt",
4
- "version": "4.5.0-rc.2",
4
+ "version": "4.5.0-rc.3",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
@@ -24,8 +24,7 @@ import {
24
24
  } from "../../messages/composables/chat";
25
25
  import {
26
26
  ensureConversationUsesSelectedModel,
27
- MODEL_UPDATE_FAILED_MESSAGE,
28
- resolveChatModelSelection
27
+ MODEL_UPDATE_FAILED_MESSAGE
29
28
  } from "../../messages/model-availability";
30
29
  import { useChatAction } from "../../messages/composables/chat-action";
31
30
  import { useChatApi } from "../../shared/composables/chat-api";
@@ -291,7 +290,7 @@ async function waitForPendingReasoningEffortUpdate() {
291
290
  }
292
291
  }
293
292
  function resolveSelectedModel(value) {
294
- return nativeModels ? nativeModels.resolve(value) : resolveChatModelSelection(value, embedConfig?.availableModels.value);
293
+ return nativeModels ? nativeModels.resolve(value) : value;
295
294
  }
296
295
  const selectedModel = computed({
297
296
  get: () => resolveSelectedModel(
@@ -581,7 +580,6 @@ async function dispatchMessage(outbound, files, resolvedAgentInputs) {
581
580
  return false;
582
581
  let claimedWakeSessionId;
583
582
  const sent = await sendMessage(outbound, files.length > 0 ? files : void 0, resolvedAgentInputs, async (signal) => {
584
- await nativeModels?.ensureReady();
585
583
  if (!activeConversationId.value || !currentConversation.value) {
586
584
  const conversation2 = await createConversation({
587
585
  model: selectedModel.value,
@@ -669,7 +667,6 @@ async function handleCancel() {
669
667
  }
670
668
  async function handleRetry(messageId) {
671
669
  await retryMessage(messageId, async () => {
672
- await nativeModels?.ensureReady();
673
670
  await waitForPendingModelUpdate();
674
671
  await waitForPendingReasoningEffortUpdate();
675
672
  const conversation = currentConversation.value;
@@ -102,6 +102,7 @@ provideEmbedConfig({
102
102
  messageFeedback: computed(() => props.messageFeedback)
103
103
  }, { syncGlobal: !parentEmbedConfig });
104
104
  provideChatRequestContext({
105
+ conversationId: selectedConversationId,
105
106
  harness,
106
107
  workspaceId,
107
108
  telaAgentId: normalizedTelaAgentId,
@@ -1,6 +1,5 @@
1
1
  import type { useChat } from '../../messages/composables/chat.js';
2
2
  type ChatModelPolicy = {
3
- ensureReady: () => Promise<void>;
4
3
  resolve: (value: string | null) => string | null;
5
4
  ensureSelectedModel: (current: string | null, selected: string | null, persist: (value: string | null) => Promise<boolean>) => Promise<void>;
6
5
  };
@@ -86,14 +86,13 @@ export type ChatEmbedSharedProps = {
86
86
  */
87
87
  defaultConversationCreatorFilter?: ConversationCreatorFilter;
88
88
  /**
89
- * Optional non-empty ordered subset of the models served for the active workspace.
90
- * Unknown ids fail at runtime. A restored unavailable selection stays visible and
91
- * blocked until the user chooses an available model.
89
+ * Visual filter and ordering for the Agent catalog. If no ids match, the full
90
+ * catalog is shown. Does not change defaults, restored selections or execution;
91
+ * the Agent validates model availability.
92
92
  */
93
93
  availableModels?: AvailableChatModels;
94
94
  /**
95
95
  * Initial model for untouched drafts. Existing conversations and explicit selections take precedence.
96
- * When excluded by availableModels, the first allowed model is used.
97
96
  */
98
97
  defaultModel?: string;
99
98
  /**
@@ -1,5 +1,4 @@
1
1
  export declare function useLightweightModelSelection(): {
2
- ensureReady: () => Promise<void>;
3
- resolve: (selection: string | null) => any;
2
+ resolve: (selection: string | null) => string | null;
4
3
  ensureSelectedModel: (current: string | null, selected: string | null, persist: (value: string | null) => Promise<boolean>) => Promise<void>;
5
4
  };
@@ -1,29 +1,12 @@
1
- import { useEmbedConfig } from "../../../embed/composables/embed-config.js";
2
1
  import { useChatModelCatalog } from "../../messages/composables/chat-model-catalog.js";
3
- import { ensureConversationUsesSelectedModel, resolveAvailableChatModels } from "../../../messages/model-availability.js";
2
+ import { ensureConversationUsesSelectedModel } from "../../../messages/model-availability.js";
4
3
  export function useLightweightModelSelection() {
5
- const embedConfig = useEmbedConfig();
6
- const { scopeKey, models, defaultModel, isLoaded, load } = useChatModelCatalog();
7
- let ready = Promise.resolve();
8
- const failed = ref(false);
4
+ const { scopeKey, load } = useChatModelCatalog();
9
5
  watch(scopeKey, () => {
10
- const scope = scopeKey.value;
11
- failed.value = false;
12
- ready = load().catch(() => {
13
- if (scopeKey.value === scope)
14
- failed.value = true;
15
- });
6
+ void load();
16
7
  }, { immediate: true });
17
- const available = computed(() => models.value.length ? resolveAvailableChatModels(embedConfig?.availableModels.value, models.value.map((model) => model.id)) : []);
18
- const fallback = computed(() => available.value.includes(defaultModel.value ?? "") ? defaultModel.value : available.value[0] ?? null);
19
- async function ensureReady() {
20
- await ready;
21
- if (failed.value || !isLoaded.value || !models.value.length)
22
- throw new Error("N\xE3o foi poss\xEDvel carregar os modelos dispon\xEDveis neste workspace");
23
- }
24
8
  return {
25
- ensureReady,
26
- resolve: (selection) => selection ?? fallback.value,
27
- ensureSelectedModel: (current, selected, persist) => ensureConversationUsesSelectedModel(current, selected, persist, available.value)
9
+ resolve: (selection) => selection,
10
+ ensureSelectedModel: (current, selected, persist) => ensureConversationUsesSelectedModel(current, selected, persist)
28
11
  };
29
12
  }
@@ -1,10 +1,8 @@
1
1
  export declare function useChatModelCatalog(): {
2
- scopeKey: any;
3
2
  models: any;
4
3
  defaultModel: any;
5
- isLoaded: any;
6
- isLoading: any;
7
- load: (options?: {
8
- force?: boolean;
9
- }) => Promise<void>;
4
+ scopeKey: Readonly<import("vue").Ref<string, string>>;
5
+ isLoaded: Readonly<import("vue").Ref<boolean, boolean>>;
6
+ isLoading: Readonly<import("vue").Ref<any, any>>;
7
+ load: () => Promise<void>;
10
8
  };
@@ -1,55 +1,8 @@
1
1
  import { chatModelsResponseSchema } from "../../../types/schemas/chat/lightweight-models.js";
2
- import { useChatApi } from "../../../shared/composables/chat-api.js";
3
- const pendingLoadsByCatalogState = /* @__PURE__ */ new WeakMap();
2
+ import { useModelCatalogCache } from "../../../messages/composables/model-catalog-cache.js";
4
3
  export function useChatModelCatalog() {
5
- const chatApi = useChatApi();
6
- const catalogs = useState("chat-model-catalogs", () => ({}));
7
- const loadedCatalogs = useState("chat-model-catalogs-loaded", () => ({}));
8
- const loadingCatalogs = useState("chat-model-catalogs-loading", () => ({}));
9
- let pendingLoads = pendingLoadsByCatalogState.get(catalogs);
10
- if (!pendingLoads) {
11
- pendingLoads = /* @__PURE__ */ new Map();
12
- pendingLoadsByCatalogState.set(catalogs, pendingLoads);
13
- }
14
- const catalogKey = computed(
15
- () => chatApi.withChatHeaders().headers?.["x-chat-workspace-id"] ?? "__default__"
16
- );
17
- const models = computed(() => (catalogs.value[catalogKey.value] ?? []).filter((model) => model.status === "available"));
18
- const isLoaded = computed(() => loadedCatalogs.value[catalogKey.value] ?? false);
19
- const isLoading = computed(() => loadingCatalogs.value[catalogKey.value] ?? false);
20
- const defaultModel = computed(
21
- () => models.value.find((model) => model.isDefault)?.id ?? models.value[0]?.id ?? null
22
- );
23
- async function load(options = {}) {
24
- const requestOptions = chatApi.withChatHeaders();
25
- const key = requestOptions.headers?.["x-chat-workspace-id"] ?? "__default__";
26
- const pending = pendingLoads.get(key);
27
- if (pending)
28
- return pending;
29
- if (loadedCatalogs.value[key] && !options.force)
30
- return;
31
- loadingCatalogs.value = { ...loadingCatalogs.value, [key]: true };
32
- const request = Promise.resolve().then(async () => {
33
- const response = await $fetch(chatApi.path("/models"), requestOptions);
34
- catalogs.value = {
35
- ...catalogs.value,
36
- [key]: chatModelsResponseSchema.parse(response).models
37
- };
38
- loadedCatalogs.value = { ...loadedCatalogs.value, [key]: true };
39
- }).finally(() => {
40
- loadingCatalogs.value = { ...loadingCatalogs.value, [key]: false };
41
- if (pendingLoads.get(key) === request)
42
- pendingLoads.delete(key);
43
- });
44
- pendingLoads.set(key, request);
45
- return request;
46
- }
47
- return {
48
- scopeKey: readonly(catalogKey),
49
- models: readonly(models),
50
- defaultModel,
51
- isLoaded: readonly(isLoaded),
52
- isLoading: readonly(isLoading),
53
- load
54
- };
4
+ const catalog = useModelCatalogCache("lightweight", (response) => chatModelsResponseSchema.parse(response).models, []);
5
+ const models = computed(() => catalog.models.value.filter((model) => model.status === "available"));
6
+ const defaultModel = computed(() => models.value.find((model) => model.isDefault)?.id ?? models.value[0]?.id ?? null);
7
+ return { ...catalog, models: readonly(models), defaultModel };
55
8
  }
@@ -7,6 +7,10 @@ type BuildChatModelOptionsInput = {
7
7
  providerIcons: Readonly<Record<string, Component>>;
8
8
  };
9
9
  export declare function buildChatModelOptions(input: BuildChatModelOptionsInput): ({
10
+ value: string;
11
+ label: string;
12
+ tabs: string[];
13
+ } | {
10
14
  isMultiModal: any;
11
15
  tabs: string[];
12
16
  maxOutputTokens?: any;
@@ -17,11 +21,5 @@ export declare function buildChatModelOptions(input: BuildChatModelOptionsInput)
17
21
  icon: Component | undefined;
18
22
  cost: any;
19
23
  maxInputTokens: any;
20
- } | {
21
- value: string;
22
- label: string;
23
- description: string;
24
- group: string;
25
- tabs: string[];
26
24
  })[];
27
25
  export {};
@@ -1,15 +1,10 @@
1
1
  import { resolveAvailableChatModels } from "../../messages/model-availability.js";
2
2
  export function buildChatModelOptions(input) {
3
- if (input.models.length === 0)
4
- return [];
5
- const availableModels = resolveAvailableChatModels(
6
- input.availableModels,
7
- input.models.map((candidate) => candidate.id)
8
- );
3
+ const availableModels = resolveAvailableChatModels(input.availableModels, input.models.map((candidate) => candidate.id));
9
4
  const options = availableModels.map((modelId) => {
10
5
  const model = input.models.find((candidate) => candidate.id === modelId);
11
6
  if (!model)
12
- throw new Error(`${modelId} is missing from the served model catalog`);
7
+ return { value: modelId, label: modelId, tabs: ["all"] };
13
8
  const isMultiModal = model.inputModalities.some((modality) => modality !== "text");
14
9
  return {
15
10
  value: model.id,
@@ -32,9 +27,7 @@ export function buildChatModelOptions(input) {
32
27
  return options;
33
28
  return [{
34
29
  value: input.selectedModel,
35
- label: `${input.selectedModel} (Indispon\xEDvel)`,
36
- description: "Selecione um modelo dispon\xEDvel antes de enviar uma mensagem.",
37
- group: "Indispon\xEDvel",
30
+ label: input.selectedModel,
38
31
  tabs: ["all"]
39
32
  }, ...options];
40
33
  }
@@ -1,8 +1,9 @@
1
1
  <script setup>
2
- import { chatModelOptions } from "../../types/schemas/chat/ui.js";
2
+ import { chatModelTabs } from "../../types/schemas/chat/model-catalog.js";
3
3
  import { useEmbedConfig } from "../../embed/composables/embed-config";
4
+ import { useChatModelCatalog } from "../composables/chat-model-catalog";
4
5
  import { useChatRequestContext } from "../../shared/composables/chat-request-context";
5
- import { useChatModelCatalog } from "../../lightweight/messages/composables/chat-model-catalog";
6
+ import { useChatModelCatalog as useLightweightModelCatalog } from "../../lightweight/messages/composables/chat-model-catalog";
6
7
  import { buildChatModelOptions } from "../../lightweight/messages/model-options";
7
8
  import { resolveAvailableChatModels } from "../model-availability";
8
9
  import TelaChatIconsAnthropic from "@meistrari/tela-build/components/tela/chat/icons/anthropic.vue";
@@ -13,12 +14,29 @@ defineProps({
13
14
  });
14
15
  const model = defineModel({ type: [String, null], ...{ default: null } });
15
16
  const embedConfig = useEmbedConfig();
16
- const lightweightCatalog = useChatRequestContext()?.harness?.value === "lightweight" ? useChatModelCatalog() : null;
17
+ const isLightweight = useChatRequestContext()?.harness?.value === "lightweight";
18
+ const lightweightCatalog = isLightweight ? useLightweightModelCatalog() : null;
19
+ const catalog = isLightweight ? null : useChatModelCatalog();
20
+ const activeCatalog = lightweightCatalog ?? catalog;
17
21
  const providerIcons = {
18
22
  Anthropic: TelaChatIconsAnthropic,
19
23
  OpenAI: TelaChatIconsOpenai,
20
24
  Google: TelaChatIconsGemini
21
25
  };
26
+ function toOption(entry) {
27
+ return {
28
+ value: entry.id,
29
+ label: entry.label,
30
+ description: entry.description ?? void 0,
31
+ group: entry.provider,
32
+ icon: providerIcons[entry.provider],
33
+ cost: entry.cost,
34
+ maxInputTokens: entry.maxInputTokens,
35
+ maxOutputTokens: entry.maxOutputTokens ?? void 0,
36
+ isMultiModal: entry.isMultiModal,
37
+ tabs: chatModelTabs(entry)
38
+ };
39
+ }
22
40
  const options = computed(() => {
23
41
  if (lightweightCatalog) {
24
42
  return buildChatModelOptions({
@@ -28,23 +46,13 @@ const options = computed(() => {
28
46
  providerIcons
29
47
  });
30
48
  }
31
- const availableModels = resolveAvailableChatModels(embedConfig?.availableModels.value);
32
- return availableModels.map((model2) => {
33
- const option = chatModelOptions.find((candidate) => candidate.value === model2);
34
- if (!option)
35
- throw new Error(`${model2} is missing from the chat model catalog`);
36
- return {
37
- value: option.value,
38
- label: option.label,
39
- description: option.description,
40
- group: option.provider,
41
- icon: providerIcons[option.provider],
42
- cost: option.cost,
43
- maxInputTokens: option.maxInputTokens,
44
- maxOutputTokens: option.maxOutputTokens,
45
- isMultiModal: option.isMultiModal,
46
- tabs: [...option.tabs]
47
- };
49
+ if (!catalog)
50
+ return [];
51
+ const availableModels = resolveAvailableChatModels(embedConfig?.availableModels.value, catalog.modelIds.value);
52
+ const optionIds = model.value && !availableModels.includes(model.value) ? [model.value, ...availableModels] : availableModels;
53
+ return optionIds.map((id) => {
54
+ const entry = catalog.models.value.find((candidate) => candidate.id === id);
55
+ return entry ? toOption(entry) : { value: id, label: id, tabs: ["all"] };
48
56
  });
49
57
  });
50
58
  const tabLabels = {
@@ -53,6 +61,9 @@ const tabLabels = {
53
61
  cheap: "Econ\xF4micos",
54
62
  multimodal: "Multimodal"
55
63
  };
64
+ watch(activeCatalog.scopeKey, () => {
65
+ void activeCatalog.load();
66
+ }, { immediate: true });
56
67
  </script>
57
68
 
58
69
  <template>
@@ -0,0 +1,8 @@
1
+ export declare function useChatModelCatalog(): {
2
+ modelIds: any;
3
+ scopeKey: Readonly<import("vue").Ref<string, string>>;
4
+ models: Readonly<import("vue").Ref<any, any>>;
5
+ isLoaded: Readonly<import("vue").Ref<boolean, boolean>>;
6
+ isLoading: Readonly<import("vue").Ref<any, any>>;
7
+ load: () => Promise<void>;
8
+ };
@@ -0,0 +1,9 @@
1
+ import { chatModelCatalogResponseSchema, fallbackChatModelCatalog } from "../../types/schemas/chat/model-catalog.js";
2
+ import { useModelCatalogCache } from "./model-catalog-cache.js";
3
+ export function useChatModelCatalog() {
4
+ const catalog = useModelCatalogCache("claude", (response) => {
5
+ const parsed = chatModelCatalogResponseSchema.parse(response);
6
+ return parsed.catalogAvailable ? parsed.models : null;
7
+ }, fallbackChatModelCatalog);
8
+ return { ...catalog, modelIds: computed(() => catalog.models.value.map((model) => model.id)) };
9
+ }
@@ -420,9 +420,7 @@ export function useChat(options = {}, adapter = {}) {
420
420
  const buckets = pendingModelBuckets.value;
421
421
  if (chatScope.value in buckets)
422
422
  return buckets[chatScope.value] ?? null;
423
- const model = embedConfig?.defaultModel?.value ?? (adapter.defaultModel === void 0 ? defaultChatModel : adapter.defaultModel);
424
- const allowed = embedConfig?.availableModels?.value;
425
- return model && allowed?.length && !allowed.includes(model) ? allowed[0] : model;
423
+ return embedConfig?.defaultModel?.value ?? (adapter.defaultModel === void 0 ? defaultChatModel : adapter.defaultModel);
426
424
  },
427
425
  set: (value) => {
428
426
  pendingModelBuckets.value = {
@@ -0,0 +1,7 @@
1
+ export declare function useModelCatalogCache<T>(harness: 'claude' | 'lightweight', parse: (response: unknown) => T[] | null, fallback: readonly T[]): {
2
+ scopeKey: Readonly<import("vue").Ref<string, string>>;
3
+ models: Readonly<import("vue").Ref<any, any>>;
4
+ isLoaded: Readonly<import("vue").Ref<boolean, boolean>>;
5
+ isLoading: Readonly<import("vue").Ref<any, any>>;
6
+ load: () => Promise<void>;
7
+ };
@@ -0,0 +1,81 @@
1
+ import { computed, getCurrentInstance, onMounted, onUnmounted, readonly } from "vue";
2
+ import { useChatApi } from "../../shared/composables/chat-api.js";
3
+ const FRESH_MS = 5 * 6e4;
4
+ const RETAIN_MS = 24 * 60 * 6e4;
5
+ const pendingByState = /* @__PURE__ */ new WeakMap();
6
+ export function useModelCatalogCache(harness, parse, fallback) {
7
+ const chatApi = useChatApi();
8
+ const entries = useState(`chat-model-catalog-${harness}`, () => ({}));
9
+ const loading = useState(`chat-model-catalog-${harness}-loading`, () => ({}));
10
+ let pending = pendingByState.get(entries.value);
11
+ if (!pending) {
12
+ pending = /* @__PURE__ */ new Map();
13
+ pendingByState.set(entries.value, pending);
14
+ }
15
+ function requestContext() {
16
+ const conversationId = chatApi.conversationId?.value;
17
+ const options = chatApi.withChatHeaders({
18
+ ...conversationId ? { query: { conversationId } } : {},
19
+ // This deadline also covers proxying and harness resolution before the upstream catalog timeout starts.
20
+ timeout: 3e4,
21
+ retry: 0
22
+ });
23
+ const headers = options.headers;
24
+ const key = JSON.stringify([
25
+ harness,
26
+ headers?.["x-chat-workspace-id"],
27
+ headers?.["x-chat-tela-agent-id"],
28
+ headers?.["x-chat-tela-agent-branch"],
29
+ headers?.["x-chat-conversation-scope"],
30
+ conversationId
31
+ ]);
32
+ return { key, options };
33
+ }
34
+ const scopeKey = computed(() => requestContext().key);
35
+ const models = computed(() => entries.value[scopeKey.value]?.models ?? fallback);
36
+ const isLoaded = computed(() => Boolean(entries.value[scopeKey.value]?.models));
37
+ const isLoading = computed(() => loading.value[scopeKey.value] ?? false);
38
+ async function load() {
39
+ const { key, options } = requestContext();
40
+ const now = Date.now();
41
+ for (const [entryKey, entry] of Object.entries(entries.value)) {
42
+ if (now - (entry.updatedAt ?? entry.attemptedAt) >= RETAIN_MS)
43
+ delete entries.value[entryKey];
44
+ }
45
+ const cached = entries.value[key];
46
+ if (cached?.updatedAt !== void 0 && now - cached.updatedAt < FRESH_MS)
47
+ return;
48
+ if (pending.has(key))
49
+ return pending.get(key);
50
+ if (cached && now < cached.retryAt)
51
+ return;
52
+ loading.value[key] = true;
53
+ const request = Promise.resolve().then(async () => {
54
+ try {
55
+ const result = parse(await $fetch(chatApi.path("/models"), options));
56
+ if (!result?.length)
57
+ throw new Error("Model catalog unavailable");
58
+ entries.value[key] = { models: result, updatedAt: Date.now(), attemptedAt: Date.now(), retryAt: 0, failures: 0 };
59
+ } catch {
60
+ const failures = (cached?.failures ?? 0) + 1;
61
+ const retained = cached?.updatedAt !== void 0 && Date.now() - cached.updatedAt < RETAIN_MS ? cached : void 0;
62
+ entries.value[key] = { ...retained, failures, attemptedAt: Date.now(), retryAt: Date.now() + Math.min(3e4 * 2 ** (failures - 1), FRESH_MS) };
63
+ } finally {
64
+ delete loading.value[key];
65
+ pending.delete(key);
66
+ }
67
+ });
68
+ pending.set(key, request);
69
+ return request;
70
+ }
71
+ if (getCurrentInstance()) {
72
+ let refresh;
73
+ onMounted(() => {
74
+ refresh = setInterval(() => {
75
+ void load();
76
+ }, FRESH_MS);
77
+ });
78
+ onUnmounted(() => clearInterval(refresh));
79
+ }
80
+ return { scopeKey: readonly(scopeKey), models: readonly(models), isLoaded: readonly(isLoaded), isLoading: readonly(isLoading), load };
81
+ }
@@ -1,8 +1,5 @@
1
1
  import type { ChatModel } from '../types/schemas/chat/models.js';
2
2
  export type AvailableChatModels = readonly [ChatModel, ...ChatModel[]];
3
3
  export declare const MODEL_UPDATE_FAILED_MESSAGE = "N\u00E3o foi poss\u00EDvel atualizar o modelo da conversa";
4
- export declare const MODEL_UNAVAILABLE_MESSAGE = "O modelo selecionado n\u00E3o est\u00E1 dispon\u00EDvel neste workspace";
5
- export declare function resolveAvailableChatModels(availableModels?: readonly string[]): readonly ChatModel[];
6
- export declare function resolveAvailableChatModels(availableModels: readonly string[] | undefined, catalogModels: readonly string[]): readonly string[];
7
- export declare function resolveChatModelSelection(selectedModel: string | null, availableModels?: readonly string[]): string | null;
8
- export declare function ensureConversationUsesSelectedModel(conversationModel: string | null, selectedModel: string | null, updateModel: (model: string | null) => Promise<boolean>, availableModels?: readonly string[]): Promise<void>;
4
+ export declare function resolveAvailableChatModels(availableModels?: readonly string[], catalogModels?: readonly string[]): readonly string[];
5
+ export declare function ensureConversationUsesSelectedModel(conversationModel: string | null, selectedModel: string | null, updateModel: (model: string | null) => Promise<boolean>): Promise<void>;
@@ -1,36 +1,13 @@
1
1
  import { chatModelIds } from "../types/schemas/chat/models.js";
2
2
  export const MODEL_UPDATE_FAILED_MESSAGE = "N\xE3o foi poss\xEDvel atualizar o modelo da conversa";
3
- export const MODEL_UNAVAILABLE_MESSAGE = "O modelo selecionado n\xE3o est\xE1 dispon\xEDvel neste workspace";
4
- export function resolveAvailableChatModels(availableModels, catalogModels) {
5
- const catalog = catalogModels ?? chatModelIds;
6
- if (catalog.length === 0)
7
- throw new Error("Agent API model catalog must contain at least one model");
3
+ export function resolveAvailableChatModels(availableModels, catalogModels = chatModelIds) {
4
+ const catalog = [...new Set(catalogModels)];
8
5
  if (availableModels === void 0)
9
- return [...new Set(catalog)];
10
- if (catalogModels) {
11
- const offeredModels = [...new Set(availableModels.filter((model) => catalog.includes(model)))];
12
- return offeredModels.length ? offeredModels : [...new Set(catalog)];
13
- }
14
- if (availableModels.length === 0)
15
- throw new Error("availableModels must contain at least one model");
16
- const uniqueModels = [];
17
- for (const model of availableModels) {
18
- if (!catalog.includes(model))
19
- throw new Error(`${model} is not a supported chat model`);
20
- if (!uniqueModels.includes(model))
21
- uniqueModels.push(model);
22
- }
23
- return uniqueModels;
6
+ return catalog;
7
+ const offeredModels = [...new Set(availableModels.filter((model) => catalog.includes(model)))];
8
+ return offeredModels.length ? offeredModels : catalog;
24
9
  }
25
- export function resolveChatModelSelection(selectedModel, availableModels) {
26
- if (availableModels === void 0)
27
- return selectedModel;
28
- const resolvedModels = resolveAvailableChatModels(availableModels);
29
- return selectedModel && resolvedModels.includes(selectedModel) ? selectedModel : resolvedModels[0];
30
- }
31
- export async function ensureConversationUsesSelectedModel(conversationModel, selectedModel, updateModel, availableModels) {
32
- if (selectedModel && availableModels && !availableModels.includes(selectedModel))
33
- throw new Error(MODEL_UNAVAILABLE_MESSAGE);
10
+ export async function ensureConversationUsesSelectedModel(conversationModel, selectedModel, updateModel) {
34
11
  if (conversationModel === selectedModel)
35
12
  return;
36
13
  if (!await updateModel(selectedModel))
@@ -1,5 +1,6 @@
1
1
  export declare function useChatApi(): {
2
2
  path: (routePath: string) => string;
3
+ conversationId: import("vue").ComputedRef<string | null>;
3
4
  runtimePath: (routePath: string) => string;
4
5
  conversationV2Enabled: boolean;
5
6
  withChatHeaders: <const T extends Record<string, any>>(options?: T) => T & {
@@ -1,3 +1,4 @@
1
+ import { computed } from "vue";
1
2
  import { useChatAuth } from "#chat-auth";
2
3
  import { useChatRequestContext } from "./chat-request-context.js";
3
4
  export function useChatApi() {
@@ -38,6 +39,7 @@ export function useChatApi() {
38
39
  }
39
40
  return {
40
41
  path,
42
+ conversationId: computed(() => requestContext?.conversationId?.value ?? null),
41
43
  runtimePath: (routePath) => `/api/v2${routePath}`,
42
44
  conversationV2Enabled: true,
43
45
  withChatHeaders
@@ -1,6 +1,7 @@
1
1
  import type { ChatHarness } from '../../types/schemas/chat/chat-config.js';
2
2
  import type { MaybeRefOrGetter, Ref } from 'vue';
3
3
  export type ChatRequestContext = {
4
+ conversationId?: Ref<string | null>;
4
5
  harness?: Ref<ChatHarness | null>;
5
6
  workspaceId: Ref<string>;
6
7
  telaAgentId: Ref<string | null>;
@@ -8,6 +9,7 @@ export type ChatRequestContext = {
8
9
  conversationScope: Ref<string | null>;
9
10
  };
10
11
  type ChatRequestContextInput = {
12
+ conversationId?: MaybeRefOrGetter<string | null | undefined>;
11
13
  harness?: MaybeRefOrGetter<ChatHarness | null>;
12
14
  workspaceId: MaybeRefOrGetter<string | null | undefined>;
13
15
  telaAgentId?: MaybeRefOrGetter<string | null | undefined>;
@@ -5,6 +5,7 @@ function normalizeOptionalHeaderValue(value) {
5
5
  }
6
6
  function useGlobalChatRequestContextState() {
7
7
  return {
8
+ conversationId: useState("chat-request-context-conversation-id", () => null),
8
9
  harness: useState("chat-request-context-harness", () => null),
9
10
  provided: useState("chat-request-context-provided", () => false),
10
11
  workspaceId: useState("chat-request-context-workspace-id", () => ""),
@@ -15,6 +16,7 @@ function useGlobalChatRequestContextState() {
15
16
  }
16
17
  export function provideChatRequestContext(input, options = {}) {
17
18
  const context = {
19
+ conversationId: computed(() => normalizeOptionalHeaderValue(toValue(input.conversationId))),
18
20
  harness: computed(() => toValue(input.harness) ?? null),
19
21
  workspaceId: computed(() => normalizeOptionalHeaderValue(toValue(input.workspaceId)) ?? ""),
20
22
  telaAgentId: computed(() => normalizeOptionalHeaderValue(toValue(input.telaAgentId))),
@@ -24,6 +26,7 @@ export function provideChatRequestContext(input, options = {}) {
24
26
  const globalContext = useGlobalChatRequestContextState();
25
27
  if (options.syncGlobal ?? true) {
26
28
  watchEffect(() => {
29
+ globalContext.conversationId.value = context.conversationId.value;
27
30
  globalContext.harness.value = context.harness.value;
28
31
  globalContext.provided.value = true;
29
32
  globalContext.workspaceId.value = context.workspaceId.value;
@@ -42,6 +45,7 @@ export function useChatRequestContext() {
42
45
  if (!globalContext.provided.value)
43
46
  return null;
44
47
  return {
48
+ conversationId: computed(() => globalContext.conversationId.value),
45
49
  harness: computed(() => globalContext.harness.value),
46
50
  workspaceId: computed(() => globalContext.workspaceId.value),
47
51
  telaAgentId: computed(() => globalContext.telaAgentId.value),
@@ -230,17 +230,7 @@ export declare const conversationWithMessagesSchema: z.ZodObject<{
230
230
  }, z.core.$strip>;
231
231
  export type ConversationWithMessages = z.infer<typeof conversationWithMessagesSchema>;
232
232
  export declare const createConversationRequestSchema: z.ZodObject<{
233
- model: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
234
- "claude-opus-5": "claude-opus-5";
235
- "claude-opus-5-5": "claude-opus-5-5";
236
- "claude-opus-4-8": "claude-opus-4-8";
237
- "claude-sonnet-5": "claude-sonnet-5";
238
- "claude-sonnet-4-6": "claude-sonnet-4-6";
239
- "claude-haiku-4-5": "claude-haiku-4-5";
240
- "openai/gpt-5.6-sol": "openai/gpt-5.6-sol";
241
- "openai/gpt-5.6-terra": "openai/gpt-5.6-terra";
242
- "openai/gpt-5.6-luna": "openai/gpt-5.6-luna";
243
- }>>>;
233
+ model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
244
234
  reasoningEffort: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
245
235
  low: "low";
246
236
  medium: "medium";
@@ -432,17 +422,7 @@ export declare const updateConversationAgentInputsResponseSchema: z.ZodObject<{
432
422
  export type UpdateConversationAgentInputsResponse = z.infer<typeof updateConversationAgentInputsResponseSchema>;
433
423
  export declare const updateConversationRequestSchema: z.ZodObject<{
434
424
  title: z.ZodOptional<z.ZodString>;
435
- model: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
436
- "claude-opus-5": "claude-opus-5";
437
- "claude-opus-5-5": "claude-opus-5-5";
438
- "claude-opus-4-8": "claude-opus-4-8";
439
- "claude-sonnet-5": "claude-sonnet-5";
440
- "claude-sonnet-4-6": "claude-sonnet-4-6";
441
- "claude-haiku-4-5": "claude-haiku-4-5";
442
- "openai/gpt-5.6-sol": "openai/gpt-5.6-sol";
443
- "openai/gpt-5.6-terra": "openai/gpt-5.6-terra";
444
- "openai/gpt-5.6-luna": "openai/gpt-5.6-luna";
445
- }>>>;
425
+ model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
446
426
  reasoningEffort: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
447
427
  low: "low";
448
428
  medium: "medium";
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ export declare const chatModelCatalogEntrySchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ label: z.ZodString;
5
+ description: z.ZodNullable<z.ZodString>;
6
+ provider: z.ZodString;
7
+ cost: z.ZodNumber;
8
+ maxInputTokens: z.ZodNumber;
9
+ maxOutputTokens: z.ZodNullable<z.ZodNumber>;
10
+ isMultiModal: z.ZodBoolean;
11
+ }, z.core.$strip>;
12
+ export type ChatModelCatalogEntry = z.infer<typeof chatModelCatalogEntrySchema>;
13
+ export declare const chatModelCatalogResponseSchema: z.ZodObject<{
14
+ models: z.ZodArray<z.ZodObject<{
15
+ id: z.ZodString;
16
+ label: z.ZodString;
17
+ description: z.ZodNullable<z.ZodString>;
18
+ provider: z.ZodString;
19
+ cost: z.ZodNumber;
20
+ maxInputTokens: z.ZodNumber;
21
+ maxOutputTokens: z.ZodNullable<z.ZodNumber>;
22
+ isMultiModal: z.ZodBoolean;
23
+ }, z.core.$strip>>;
24
+ catalogAvailable: z.ZodBoolean;
25
+ }, z.core.$strip>;
26
+ export type ChatModelCatalogResponse = z.infer<typeof chatModelCatalogResponseSchema>;
27
+ export declare const fallbackChatModelCatalog: readonly ChatModelCatalogEntry[];
28
+ export declare function chatModelTabs(entry: Pick<ChatModelCatalogEntry, 'cost' | 'isMultiModal'>): string[];
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ import { chatModelOptions } from './ui';
3
+ export const chatModelCatalogEntrySchema = z.object({
4
+ id: z.string().min(1),
5
+ label: z.string().min(1),
6
+ description: z.string().nullable(),
7
+ provider: z.string().min(1),
8
+ cost: z.number().int().min(1).max(5),
9
+ maxInputTokens: z.number().int().positive(),
10
+ maxOutputTokens: z.number().int().positive().nullable(),
11
+ isMultiModal: z.boolean(),
12
+ });
13
+ export const chatModelCatalogResponseSchema = z.object({
14
+ models: z.array(chatModelCatalogEntrySchema).min(1),
15
+ // False when the bundled fallback is being served instead of the Agent API catalog.
16
+ catalogAvailable: z.boolean(),
17
+ });
18
+ export const fallbackChatModelCatalog = chatModelOptions.map(option => ({
19
+ id: option.value,
20
+ label: option.label,
21
+ description: option.description,
22
+ provider: option.provider,
23
+ cost: option.cost,
24
+ maxInputTokens: option.maxInputTokens,
25
+ maxOutputTokens: option.maxOutputTokens,
26
+ isMultiModal: option.isMultiModal,
27
+ }));
28
+ export function chatModelTabs(entry) {
29
+ return ['all', entry.cost <= 2 ? 'cheap' : 'advanced', ...(entry.isMultiModal ? ['multimodal'] : [])];
30
+ }
@@ -1,19 +1,10 @@
1
1
  import { z } from 'zod';
2
2
  export declare const chatModelIds: readonly ["claude-opus-5", "claude-opus-5-5", "claude-opus-4-8", "claude-sonnet-5", "claude-sonnet-4-6", "claude-haiku-4-5", "openai/gpt-5.6-sol", "openai/gpt-5.6-terra", "openai/gpt-5.6-luna"];
3
- export declare const chatModelSchema: z.ZodEnum<{
4
- "claude-opus-5": "claude-opus-5";
5
- "claude-opus-5-5": "claude-opus-5-5";
6
- "claude-opus-4-8": "claude-opus-4-8";
7
- "claude-sonnet-5": "claude-sonnet-5";
8
- "claude-sonnet-4-6": "claude-sonnet-4-6";
9
- "claude-haiku-4-5": "claude-haiku-4-5";
10
- "openai/gpt-5.6-sol": "openai/gpt-5.6-sol";
11
- "openai/gpt-5.6-terra": "openai/gpt-5.6-terra";
12
- "openai/gpt-5.6-luna": "openai/gpt-5.6-luna";
13
- }>;
3
+ export type KnownChatModel = typeof chatModelIds[number];
4
+ export declare const chatModelSchema: z.ZodString;
14
5
  export type ChatModel = z.infer<typeof chatModelSchema>;
15
6
  export declare const defaultChatModel = "claude-opus-5";
16
- export declare function isValidChatModel(value: unknown): value is ChatModel;
7
+ export declare function isValidChatModel(value: unknown): value is KnownChatModel;
17
8
  export declare const chatReasoningEffortValues: readonly ["low", "medium", "high", "xhigh", "max"];
18
9
  export declare const chatReasoningEffortSchema: z.ZodEnum<{
19
10
  low: "low";
@@ -1,4 +1,6 @@
1
1
  import { z } from 'zod';
2
+ // Bundled catalog: the chat-api and chat-nuxt fall back to these ids when the
3
+ // Agent API catalog cannot be fetched. The live catalog may offer more.
2
4
  export const chatModelIds = [
3
5
  'claude-opus-5',
4
6
  'claude-opus-5-5',
@@ -10,7 +12,8 @@ export const chatModelIds = [
10
12
  'openai/gpt-5.6-terra',
11
13
  'openai/gpt-5.6-luna',
12
14
  ];
13
- export const chatModelSchema = z.enum(chatModelIds);
15
+ // Any id the Agent API catalog exposes; the Agent API rejects unknown ones on execute.
16
+ export const chatModelSchema = z.string().trim().min(1);
14
17
  export const defaultChatModel = 'claude-opus-5';
15
18
  export function isValidChatModel(value) {
16
19
  return typeof value === 'string' && chatModelIds.includes(value);
@@ -8,6 +8,7 @@ export * from './chat/github';
8
8
  export { chatModelCatalogEntrySchema, chatModelCatalogResponseSchema, chatModelsResponseSchema, isValidChatModel as isValidLightweightChatModel, chatModelSchema as lightweightChatModelSchema, } from './chat/lightweight-models';
9
9
  export type { ChatModelCatalogEntry, ChatModel as LightweightChatModel } from './chat/lightweight-models';
10
10
  export * from './chat/messages';
11
+ export { chatModelTabs, fallbackChatModelCatalog } from './chat/model-catalog';
11
12
  export * from './chat/models';
12
13
  export * from './chat/primitives';
13
14
  export * from './chat/titles';
@@ -7,6 +7,7 @@ export * from './chat/files';
7
7
  export * from './chat/github';
8
8
  export { chatModelCatalogEntrySchema, chatModelCatalogResponseSchema, chatModelsResponseSchema, isValidChatModel as isValidLightweightChatModel, chatModelSchema as lightweightChatModelSchema, } from './chat/lightweight-models';
9
9
  export * from './chat/messages';
10
+ export { chatModelTabs, fallbackChatModelCatalog } from './chat/model-catalog';
10
11
  export * from './chat/models';
11
12
  export * from './chat/primitives';
12
13
  export * from './chat/titles';
@@ -274,17 +274,7 @@ export declare const telaAgentSessionSchema: z.ZodObject<{
274
274
  export type TelaAgentSession = z.infer<typeof telaAgentSessionSchema>;
275
275
  export declare const telaAgentWakeRequestSchema: z.ZodObject<{
276
276
  sessionId: z.ZodOptional<z.ZodString>;
277
- model: z.ZodOptional<z.ZodEnum<{
278
- "claude-opus-5": "claude-opus-5";
279
- "claude-opus-5-5": "claude-opus-5-5";
280
- "claude-opus-4-8": "claude-opus-4-8";
281
- "claude-sonnet-5": "claude-sonnet-5";
282
- "claude-sonnet-4-6": "claude-sonnet-4-6";
283
- "claude-haiku-4-5": "claude-haiku-4-5";
284
- "openai/gpt-5.6-sol": "openai/gpt-5.6-sol";
285
- "openai/gpt-5.6-terra": "openai/gpt-5.6-terra";
286
- "openai/gpt-5.6-luna": "openai/gpt-5.6-luna";
287
- }>>;
277
+ model: z.ZodOptional<z.ZodString>;
288
278
  }, z.core.$strip>;
289
279
  export type TelaAgentWakeRequest = z.infer<typeof telaAgentWakeRequestSchema>;
290
280
  export declare const telaAgentWakeResponseSchema: z.ZodObject<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meistrari/chat-nuxt",
3
- "version": "4.5.0-rc.2",
3
+ "version": "4.5.0-rc.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {