@meistrari/chat-nuxt 4.4.0-rc.6 → 4.4.0-rc.7
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 +15 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -0
- package/dist/runtime/embed/components/chat-embed.d.vue.ts +3 -6
- package/dist/runtime/embed/components/chat-embed.vue +44 -9
- package/dist/runtime/embed/components/chat-embed.vue.d.ts +3 -6
- package/dist/runtime/embed/components/meistrari-chat-embed.vue +0 -1
- package/dist/runtime/embed/composables/agent-harness.d.ts +15 -0
- package/dist/runtime/embed/composables/agent-harness.js +55 -0
- package/dist/runtime/embed/types.d.ts +0 -5
- package/dist/runtime/lightweight/embed/composables/model-selection.d.ts +1 -1
- package/dist/runtime/lightweight/embed/composables/model-selection.js +3 -3
- package/dist/runtime/lightweight/messages/composables/use-direct-stream.d.ts +16 -35
- package/dist/runtime/lightweight/messages/composables/use-direct-stream.js +6 -277
- package/dist/runtime/lightweight/messages/model-options.js +3 -3
- package/dist/runtime/messages/composables/use-direct-stream.d.ts +35 -2
- package/dist/runtime/messages/composables/use-direct-stream.js +17 -3
- package/dist/runtime/messages/model-availability.d.ts +3 -1
- package/dist/runtime/messages/model-availability.js +13 -6
- package/dist/runtime/shared/composables/chat-request-context.d.ts +2 -2
- package/dist/runtime/shared/composables/chat-request-context.js +2 -2
- package/package.json +1 -1
- package/dist/runtime/lightweight/messages/model-availability.d.ts +0 -7
- package/dist/runtime/lightweight/messages/model-availability.js +0 -29
package/README.md
CHANGED
|
@@ -33,6 +33,20 @@ Runtime internals under `src/runtime/**`, generated Nuxt aliases, and workspace-
|
|
|
33
33
|
| [Workspace settings](docs/guides/workspace-settings.md) | Settings overrides, agent runtime ownership, `<ChatConfigurationModal>` |
|
|
34
34
|
| [Upgrading to 4.0](docs/guides/upgrading-to-4.md) | Required Chat API version for v2 sends, upgrade order, vue-i18n removal |
|
|
35
35
|
|
|
36
|
+
## Harness selection
|
|
37
|
+
|
|
38
|
+
Select the Tela agent; its published `agent.config.json` determines the harness:
|
|
39
|
+
|
|
40
|
+
```vue
|
|
41
|
+
<MeistrariChatEmbed tela-agent-id="your-agent-id" />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The chat resolves the agent before mounting its UI. Explicit branches use that branch's configuration. Missing, unreadable or unsupported harnesses show an error and never fall back to Claude. The host cannot select a harness through props or HTTP headers.
|
|
45
|
+
|
|
46
|
+
Both harnesses share the chat screen, composer, navigation and state controller, while their protocol adapters and renderers remain separate. Selecting another agent clears the old UI and resolves its configuration again. The backend independently reads the configured harness for routed requests.
|
|
47
|
+
|
|
48
|
+
Requires Agent API v4’s `GET /v4/agents/{organizationName}/{repository}/harness` endpoint. Chat resolves the existing agent coordinates, then reads the configured harness directly from Agent API using the published commit or selected branch. The response exposes only the harness. Deploy the Agent API endpoint before this Chat version; no Tela API changes are required.
|
|
49
|
+
|
|
36
50
|
## Conversation forks
|
|
37
51
|
|
|
38
52
|
Completed assistant responses offer **Continuar em nova conversa** in the three-dot
|
|
@@ -74,7 +88,7 @@ Declared in `src/runtime/embed/types.ts`; prop, feature-flag, and event payload
|
|
|
74
88
|
| `initialConversationId` | `string \| null` | `null` | Conversation to open once on mount when `conversationId` is uncontrolled |
|
|
75
89
|
| `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`, `.`, `_`, `:`, `/`, `-` |
|
|
76
90
|
| `defaultConversationCreatorFilter` | `'all' \| 'mine'` | `'all'` | Initial sidebar creator filter: every conversation in scope, or only the current user's |
|
|
77
|
-
| `availableModels` | `AvailableChatModels` |
|
|
91
|
+
| `availableModels` | `AvailableChatModels` | Workspace catalog | Optional non-empty ordered subset of models served for the active workspace. Unknown ids fail at runtime; a restored unavailable selection stays visible and blocked until the user chooses an available model |
|
|
78
92
|
| `defaultModel` | `ChatModel` | Package default | Initial model for untouched drafts. Explicit selections and existing conversations take precedence; `availableModels` still limits the selection |
|
|
79
93
|
| `defaultReasoningEffort` | `ChatReasoningEffort` | Automatic (`null`) | Initial reasoning effort for untouched drafts. Explicit user choices (including automatic) take precedence |
|
|
80
94
|
| `conversationCreatorFilter` | `'all' \| 'mine'` | — | Controlled sidebar creator filter; use with `v-model:conversation-creator-filter` when the host changes it at runtime |
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -445,6 +445,7 @@ const module$1 = defineNuxtModule({
|
|
|
445
445
|
const rc = nuxt.options.runtimeConfig;
|
|
446
446
|
rc.chatApiUrl = options.chatApiUrl || rc.chatApiUrl || process.env.CHAT_API_URL || "";
|
|
447
447
|
rc.public = rc.public || {};
|
|
448
|
+
rc.public.chatConfig = { ...rc.public.chatConfig };
|
|
448
449
|
const existingChatNuxtRuntimeConfig = rc.chatNuxt;
|
|
449
450
|
rc.chatNuxt = {
|
|
450
451
|
chatApiUrl: existingChatNuxtRuntimeConfig?.chatApiUrl || rc.chatApiUrl,
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
declare var __VLS_13: {}, __VLS_16: {
|
|
3
|
-
message: useChatAuth;
|
|
4
|
-
};
|
|
1
|
+
declare var __VLS_26: {}, __VLS_29: any;
|
|
5
2
|
type __VLS_Slots = {} & {
|
|
6
|
-
'sidebar-bottom'?: (props: typeof
|
|
3
|
+
'sidebar-bottom'?: (props: typeof __VLS_26) => any;
|
|
7
4
|
} & {
|
|
8
|
-
'failed-message-actions'?: (props: typeof
|
|
5
|
+
'failed-message-actions'?: (props: typeof __VLS_29) => any;
|
|
9
6
|
};
|
|
10
7
|
declare const __VLS_base: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
|
|
11
8
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -8,9 +8,9 @@ import { provideChatRequestContext } from "../../shared/composables/chat-request
|
|
|
8
8
|
import { provideEmbedConfig, useEmbedConfig } from "../composables/embed-config";
|
|
9
9
|
import { normalizeConversationScope, normalizeTelaAgentId, resolveConversationScopeKeySegment } from "../../conversations/scope";
|
|
10
10
|
import ChatEmbedInner from "./chat-embed-inner.vue";
|
|
11
|
+
import { useAgentHarness } from "../composables/agent-harness";
|
|
11
12
|
const props = defineProps({
|
|
12
13
|
syncBrowserState: { type: Boolean, required: false, default: true },
|
|
13
|
-
chatConfig: { type: Object, required: false },
|
|
14
14
|
telaAgentBranch: { type: [String, null], required: false },
|
|
15
15
|
conversationId: { type: [String, null], required: false },
|
|
16
16
|
conversationEvents: { type: [Array, null], required: false },
|
|
@@ -47,11 +47,30 @@ const props = defineProps({
|
|
|
47
47
|
launcherLabel: { type: String, required: false }
|
|
48
48
|
});
|
|
49
49
|
const emit = defineEmits(["update:conversationId", "update:conversationTitle", "update:conversationCreatorFilter", "action", "messageFeedback", "messageTerminal"]);
|
|
50
|
+
const LightweightAdapter = defineAsyncComponent(() => import("../../lightweight/embed/components/chat-embed-adapter.vue"));
|
|
50
51
|
const parentEmbedConfig = useEmbedConfig();
|
|
51
52
|
const { activeOrganization } = useChatAuth();
|
|
52
53
|
const workspaceId = computed(() => activeOrganization.value?.id?.trim() ?? "");
|
|
53
54
|
const normalizedTelaAgentId = computed(() => normalizeTelaAgentId(props.telaAgentId));
|
|
54
55
|
const telaAgentBranch = computed(() => props.telaAgentBranch?.trim() || null);
|
|
56
|
+
const selectedConversationId = ref(props.conversationId ?? props.initialConversationId ?? null);
|
|
57
|
+
watch(() => props.conversationId, (value) => {
|
|
58
|
+
selectedConversationId.value = value ?? null;
|
|
59
|
+
});
|
|
60
|
+
function selectConversation(value) {
|
|
61
|
+
selectedConversationId.value = value;
|
|
62
|
+
emit("update:conversationId", value);
|
|
63
|
+
}
|
|
64
|
+
const { harness, loading: harnessLoading, error: harnessError, retry: retryHarness } = useAgentHarness({
|
|
65
|
+
workspaceId,
|
|
66
|
+
agentId: normalizedTelaAgentId,
|
|
67
|
+
branch: telaAgentBranch,
|
|
68
|
+
conversationId: selectedConversationId,
|
|
69
|
+
onConversationCleared: () => emit("update:conversationId", null),
|
|
70
|
+
conversationScope: computed(() => normalizeConversationScope(props.conversationScope))
|
|
71
|
+
});
|
|
72
|
+
const harnessComponents = { claude: ChatEmbedInner, lightweight: LightweightAdapter };
|
|
73
|
+
const harnessComponent = computed(() => harness.value ? harnessComponents[harness.value] : null);
|
|
55
74
|
const normalizedConversationScope = computed(() => normalizeConversationScope(props.conversationScope));
|
|
56
75
|
const hideSettings = computed(() => props.hideSettings ?? false);
|
|
57
76
|
const ignoredPropsWarningKey = ref(null);
|
|
@@ -83,6 +102,7 @@ provideEmbedConfig({
|
|
|
83
102
|
messageFeedback: computed(() => props.messageFeedback)
|
|
84
103
|
}, { syncGlobal: !parentEmbedConfig });
|
|
85
104
|
provideChatRequestContext({
|
|
105
|
+
harness,
|
|
86
106
|
workspaceId,
|
|
87
107
|
telaAgentId: normalizedTelaAgentId,
|
|
88
108
|
telaAgentBranch,
|
|
@@ -119,14 +139,26 @@ provideChatMessageTerminal((payload) => {
|
|
|
119
139
|
</script>
|
|
120
140
|
|
|
121
141
|
<template>
|
|
122
|
-
<
|
|
123
|
-
v-if="workspaceId"
|
|
124
|
-
|
|
142
|
+
<TelaChatEmptyState
|
|
143
|
+
v-if="workspaceId && harnessError"
|
|
144
|
+
compact
|
|
145
|
+
role="alert"
|
|
146
|
+
title="Não foi possível carregar o agente"
|
|
147
|
+
:description="harnessError"
|
|
148
|
+
>
|
|
149
|
+
<TelaButton variant="secondary" @click="retryHarness">
|
|
150
|
+
Tentar novamente
|
|
151
|
+
</TelaButton>
|
|
152
|
+
</TelaChatEmptyState>
|
|
153
|
+
<component
|
|
154
|
+
:is="harnessComponent"
|
|
155
|
+
v-if="workspaceId && harnessComponent"
|
|
156
|
+
:key="`${harness}:${workspaceId}:${normalizedTelaAgentId ?? 'default-chat'}:branch:${telaAgentBranch ?? ''}:scope:${resolveConversationScopeKeySegment(normalizedConversationScope)}`"
|
|
125
157
|
:sync-browser-state="syncBrowserState"
|
|
126
|
-
:conversation-id="
|
|
158
|
+
:conversation-id="selectedConversationId"
|
|
127
159
|
:conversation-events="conversationEvents"
|
|
128
|
-
:composer-disabled="composerDisabled"
|
|
129
|
-
:initial-conversation-id="
|
|
160
|
+
:composer-disabled="composerDisabled || harnessLoading || !!harnessError"
|
|
161
|
+
:initial-conversation-id="selectedConversationId"
|
|
130
162
|
:hide-sidebar="hideSidebar"
|
|
131
163
|
:sidebar="sidebar"
|
|
132
164
|
:hide-settings="hideSettings"
|
|
@@ -134,7 +166,7 @@ provideChatMessageTerminal((payload) => {
|
|
|
134
166
|
:suggestions="suggestions"
|
|
135
167
|
:user="normalizedTelaAgentId ? null : user"
|
|
136
168
|
:features="features"
|
|
137
|
-
@update:conversation-id="
|
|
169
|
+
@update:conversation-id="selectConversation"
|
|
138
170
|
@update:conversation-title="$emit('update:conversationTitle', $event)"
|
|
139
171
|
@update:conversation-creator-filter="$emit('update:conversationCreatorFilter', $event)"
|
|
140
172
|
>
|
|
@@ -144,5 +176,8 @@ provideChatMessageTerminal((payload) => {
|
|
|
144
176
|
<template v-if="$slots['failed-message-actions']" #failed-message-actions="slotProps">
|
|
145
177
|
<slot name="failed-message-actions" v-bind="slotProps" />
|
|
146
178
|
</template>
|
|
147
|
-
</
|
|
179
|
+
</component>
|
|
180
|
+
<TelaChatEmptyState v-else-if="workspaceId && harnessLoading" compact role="status" aria-live="polite">
|
|
181
|
+
<TelaChatTextShimmer>Carregando agente…</TelaChatTextShimmer>
|
|
182
|
+
</TelaChatEmptyState>
|
|
148
183
|
</template>
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
declare var __VLS_13: {}, __VLS_16: {
|
|
3
|
-
message: useChatAuth;
|
|
4
|
-
};
|
|
1
|
+
declare var __VLS_26: {}, __VLS_29: any;
|
|
5
2
|
type __VLS_Slots = {} & {
|
|
6
|
-
'sidebar-bottom'?: (props: typeof
|
|
3
|
+
'sidebar-bottom'?: (props: typeof __VLS_26) => any;
|
|
7
4
|
} & {
|
|
8
|
-
'failed-message-actions'?: (props: typeof
|
|
5
|
+
'failed-message-actions'?: (props: typeof __VLS_29) => any;
|
|
9
6
|
};
|
|
10
7
|
declare const __VLS_base: import("vue").DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import("vue").PublicProps>;
|
|
11
8
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
@@ -3,7 +3,6 @@ import { computed, ref, watch } from "vue";
|
|
|
3
3
|
import { provideConversationCreatorFilterState } from "../../conversations/composables/conversation-creator-filter-state";
|
|
4
4
|
const { hideSidebar = void 0, syncBrowserState = true, closeOnOutsideClick = true, ...props } = defineProps({
|
|
5
5
|
syncBrowserState: { type: Boolean, required: false },
|
|
6
|
-
chatConfig: { type: Object, required: false },
|
|
7
6
|
telaAgentBranch: { type: [String, null], required: false },
|
|
8
7
|
conversationId: { type: [String, null], required: false },
|
|
9
8
|
conversationEvents: { type: [Array, null], required: false },
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
/** Resolve the executable agent before mounting either protocol-specific UI. */
|
|
3
|
+
export declare function useAgentHarness(input: {
|
|
4
|
+
workspaceId: Ref<string>;
|
|
5
|
+
agentId: Ref<string | null>;
|
|
6
|
+
branch: Ref<string | null>;
|
|
7
|
+
conversationId?: Ref<string | null>;
|
|
8
|
+
conversationScope?: Ref<string | null>;
|
|
9
|
+
onConversationCleared?: () => void;
|
|
10
|
+
}): {
|
|
11
|
+
harness: any;
|
|
12
|
+
loading: Ref<boolean, boolean>;
|
|
13
|
+
error: Ref<string | null, string | null>;
|
|
14
|
+
retry: () => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ref, watch } from "vue";
|
|
2
|
+
export function useAgentHarness(input) {
|
|
3
|
+
const harness = ref(null);
|
|
4
|
+
const loading = ref(false);
|
|
5
|
+
const error = ref(null);
|
|
6
|
+
const retryCount = ref(0);
|
|
7
|
+
watch([input.workspaceId, input.agentId, input.branch, input.conversationScope ?? ref(null)], () => {
|
|
8
|
+
if (input.conversationId?.value) {
|
|
9
|
+
input.conversationId.value = null;
|
|
10
|
+
input.onConversationCleared?.();
|
|
11
|
+
}
|
|
12
|
+
}, { flush: "sync" });
|
|
13
|
+
watch([input.workspaceId, input.agentId, input.branch, input.conversationId ?? ref(null), input.conversationScope ?? ref(null), retryCount], async ([workspaceId, agentId, branch, conversationId, conversationScope], previous, onCleanup) => {
|
|
14
|
+
if (workspaceId !== previous?.[0] || agentId !== previous?.[1] || branch !== previous?.[2] || conversationScope !== previous?.[4]) {
|
|
15
|
+
harness.value = null;
|
|
16
|
+
}
|
|
17
|
+
error.value = null;
|
|
18
|
+
loading.value = false;
|
|
19
|
+
if (!workspaceId)
|
|
20
|
+
return;
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
onCleanup(() => controller.abort());
|
|
23
|
+
loading.value = true;
|
|
24
|
+
const headers = { "x-chat-workspace-id": workspaceId };
|
|
25
|
+
if (agentId)
|
|
26
|
+
headers["x-chat-tela-agent-id"] = agentId;
|
|
27
|
+
if (branch)
|
|
28
|
+
headers["x-chat-tela-agent-branch"] = branch;
|
|
29
|
+
if (conversationScope)
|
|
30
|
+
headers["x-chat-conversation-scope"] = conversationScope;
|
|
31
|
+
try {
|
|
32
|
+
const result = await $fetch("/api/chat/agent/runtime", {
|
|
33
|
+
headers,
|
|
34
|
+
...conversationId ? { query: { conversationId } } : {},
|
|
35
|
+
signal: controller.signal
|
|
36
|
+
});
|
|
37
|
+
if (controller.signal.aborted)
|
|
38
|
+
return;
|
|
39
|
+
if (!result?.agentId || agentId && result.agentId !== agentId || result.harness !== "claude" && result.harness !== "lightweight") {
|
|
40
|
+
throw new Error("Unsupported or missing agent harness");
|
|
41
|
+
}
|
|
42
|
+
harness.value = result.harness;
|
|
43
|
+
} catch {
|
|
44
|
+
if (!controller.signal.aborted) {
|
|
45
|
+
error.value = "N\xE3o foi poss\xEDvel carregar a configura\xE7\xE3o do agente.";
|
|
46
|
+
}
|
|
47
|
+
} finally {
|
|
48
|
+
if (!controller.signal.aborted)
|
|
49
|
+
loading.value = false;
|
|
50
|
+
}
|
|
51
|
+
}, { immediate: true, flush: "sync" });
|
|
52
|
+
return { harness, loading, error, retry: () => {
|
|
53
|
+
retryCount.value++;
|
|
54
|
+
} };
|
|
55
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ChatHarness } from '../types/schemas/chat/chat-config.js';
|
|
2
1
|
import type { ChatReasoningEffort } from '../types/schemas/chat/models.js';
|
|
3
2
|
import type { Component, DeepReadonly } from 'vue';
|
|
4
3
|
import type { ConversationEvent } from '../types/schemas/chat/conversations.js';
|
|
@@ -50,10 +49,6 @@ export type ChatSidebarConfig = {
|
|
|
50
49
|
export type ChatEmbedSharedProps = {
|
|
51
50
|
/** Let the embed synchronize its conversation with the browser URL and title. Defaults to true. */
|
|
52
51
|
syncBrowserState?: boolean;
|
|
53
|
-
/** Defaults to Claude. Lightweight uses its own execution and rendering path. */
|
|
54
|
-
chatConfig?: {
|
|
55
|
-
harness?: ChatHarness;
|
|
56
|
-
};
|
|
57
52
|
/** Optional repository branch for new agent sessions. Omit to use the published version. */
|
|
58
53
|
telaAgentBranch?: string | null;
|
|
59
54
|
/** Controlled conversation id. Use with `v-model:conversation-id` when the host owns active conversation state. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function useLightweightModelSelection(): {
|
|
2
2
|
ensureReady: () => Promise<void>;
|
|
3
|
-
resolve: (selection: string | null) =>
|
|
3
|
+
resolve: (selection: string | null) => any;
|
|
4
4
|
ensureSelectedModel: (current: string | null, selected: string | null, persist: (value: string | null) => Promise<boolean>) => Promise<void>;
|
|
5
5
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEmbedConfig } from "../../../embed/composables/embed-config.js";
|
|
2
2
|
import { useChatModelCatalog } from "../../messages/composables/chat-model-catalog.js";
|
|
3
|
-
import { ensureConversationUsesSelectedModel, resolveAvailableChatModels
|
|
3
|
+
import { ensureConversationUsesSelectedModel, resolveAvailableChatModels } from "../../../messages/model-availability.js";
|
|
4
4
|
export function useLightweightModelSelection() {
|
|
5
5
|
const embedConfig = useEmbedConfig();
|
|
6
6
|
const { scopeKey, models, defaultModel, isLoaded, load } = useChatModelCatalog();
|
|
@@ -14,7 +14,7 @@ export function useLightweightModelSelection() {
|
|
|
14
14
|
failed.value = true;
|
|
15
15
|
});
|
|
16
16
|
}, { immediate: true });
|
|
17
|
-
const available = computed(() => models.value.length ? resolveAvailableChatModels(models.value.map((model) => model.id)
|
|
17
|
+
const available = computed(() => models.value.length ? resolveAvailableChatModels(embedConfig?.availableModels.value, models.value.map((model) => model.id)) : []);
|
|
18
18
|
const fallback = computed(() => available.value.includes(defaultModel.value ?? "") ? defaultModel.value : available.value[0] ?? null);
|
|
19
19
|
async function ensureReady() {
|
|
20
20
|
await ready;
|
|
@@ -23,7 +23,7 @@ export function useLightweightModelSelection() {
|
|
|
23
23
|
}
|
|
24
24
|
return {
|
|
25
25
|
ensureReady,
|
|
26
|
-
resolve: (selection) =>
|
|
26
|
+
resolve: (selection) => selection ?? fallback.value,
|
|
27
27
|
ensureSelectedModel: (current, selected, persist) => ensureConversationUsesSelectedModel(current, selected, persist, available.value)
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -1,53 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { applyLightweightPart } from '../../../types/schemas/integrations/lightweight.js';
|
|
2
2
|
import type { LightweightTurn } from '../../../types/schemas/integrations/lightweight.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type DirectStreamResult = {
|
|
6
|
-
status: DirectStreamResultStatus;
|
|
7
|
-
text: string;
|
|
8
|
-
};
|
|
9
|
-
export type SseFrame = {
|
|
10
|
-
event?: string;
|
|
11
|
-
data: string;
|
|
12
|
-
};
|
|
13
|
-
export type MessagePartsEvent = {
|
|
14
|
-
kind: 'message-parts';
|
|
15
|
-
harness?: string;
|
|
16
|
-
sessionId?: string;
|
|
17
|
-
timestamp?: number;
|
|
18
|
-
runId: string;
|
|
19
|
-
deltaSeq: number;
|
|
20
|
-
parentToolUseId?: string | null;
|
|
21
|
-
event: Record<string, unknown>;
|
|
22
|
-
};
|
|
23
|
-
export type MessagePartsAccumulator = {
|
|
3
|
+
import type { DirectStreamOptions, MessagePartsEvent } from '../../../messages/composables/use-direct-stream.js';
|
|
4
|
+
type MessagePartsAccumulator = {
|
|
24
5
|
runId: string | null;
|
|
25
6
|
lightweight?: LightweightTurn;
|
|
26
7
|
};
|
|
27
|
-
export declare function parseSseFrames(source: string): {
|
|
28
|
-
frames: SseFrame[];
|
|
29
|
-
rest: string;
|
|
30
|
-
};
|
|
31
8
|
export declare function createMessagePartsAccumulator(runId?: string | null): MessagePartsAccumulator;
|
|
32
9
|
export declare function applyMessagePartsEvent(accumulator: MessagePartsAccumulator, input: MessagePartsEvent): {
|
|
33
|
-
text:
|
|
34
|
-
thinking:
|
|
10
|
+
text: any;
|
|
11
|
+
thinking: any;
|
|
35
12
|
changed: boolean;
|
|
13
|
+
reasoning?: undefined;
|
|
14
|
+
files?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
text: any;
|
|
17
|
+
thinking: any;
|
|
18
|
+
changed: any;
|
|
19
|
+
reasoning: any;
|
|
20
|
+
files: any;
|
|
36
21
|
};
|
|
37
|
-
export declare function useDirectStream(options?: {
|
|
38
|
-
onOptimisticResult?: (result: DirectStreamResult) => void;
|
|
39
|
-
onResult?: (result: DirectStreamResult) => void;
|
|
40
|
-
onTerminal?: () => void;
|
|
41
|
-
onDisconnect?: () => void;
|
|
42
|
-
}): {
|
|
22
|
+
export declare function useDirectStream(options?: DirectStreamOptions): {
|
|
43
23
|
activity: any;
|
|
44
24
|
streamingText: any;
|
|
45
25
|
streamingThinking: any;
|
|
46
26
|
activeRunId: any;
|
|
47
27
|
streamingReasoning: any;
|
|
48
28
|
streamingFiles: any;
|
|
49
|
-
connectMessage: (url: string, init: RequestInit, onAccepted: (response:
|
|
29
|
+
connectMessage: (url: string, init: RequestInit, onAccepted: (response: applyLightweightPart) => void) => Promise<SendMessageResponse>;
|
|
50
30
|
connectConversation: (url: string, init: RequestInit, expectedAssistantMessageId?: string) => Promise<void>;
|
|
51
31
|
beginMessage: () => void;
|
|
52
32
|
disconnect: (reset?: boolean) => void;
|
|
53
33
|
};
|
|
34
|
+
export {};
|
|
@@ -1,36 +1,5 @@
|
|
|
1
1
|
import { applyLightweightPart, createLightweightTurn } from "../../../types/schemas/integrations/lightweight.js";
|
|
2
|
-
import {
|
|
3
|
-
function isRecord(value) {
|
|
4
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
-
}
|
|
6
|
-
export function parseSseFrames(source) {
|
|
7
|
-
const frames = [];
|
|
8
|
-
let rest = source;
|
|
9
|
-
while (true) {
|
|
10
|
-
const boundary = /\r?\n\r?\n/.exec(rest);
|
|
11
|
-
if (!boundary || boundary.index === void 0)
|
|
12
|
-
break;
|
|
13
|
-
const rawFrame = rest.slice(0, boundary.index);
|
|
14
|
-
rest = rest.slice(boundary.index + boundary[0].length);
|
|
15
|
-
let event;
|
|
16
|
-
const data = [];
|
|
17
|
-
for (const line of rawFrame.split(/\r?\n/)) {
|
|
18
|
-
if (line.startsWith(":"))
|
|
19
|
-
continue;
|
|
20
|
-
const separator = line.indexOf(":");
|
|
21
|
-
const field = separator === -1 ? line : line.slice(0, separator);
|
|
22
|
-
const rawValue = separator === -1 ? "" : line.slice(separator + 1);
|
|
23
|
-
const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
|
|
24
|
-
if (field === "event")
|
|
25
|
-
event = value;
|
|
26
|
-
else if (field === "data")
|
|
27
|
-
data.push(value);
|
|
28
|
-
}
|
|
29
|
-
if (data.length > 0)
|
|
30
|
-
frames.push({ ...event ? { event } : {}, data: data.join("\n") });
|
|
31
|
-
}
|
|
32
|
-
return { frames, rest };
|
|
33
|
-
}
|
|
2
|
+
import { useDirectStreamWithProtocol } from "../../../messages/composables/use-direct-stream.js";
|
|
34
3
|
export function createMessagePartsAccumulator(runId = null) {
|
|
35
4
|
return {
|
|
36
5
|
runId
|
|
@@ -43,251 +12,11 @@ export function applyMessagePartsEvent(accumulator, input) {
|
|
|
43
12
|
accumulator.lightweight = createLightweightTurn(input.runId, input.sessionId ?? "");
|
|
44
13
|
accumulator.runId = input.runId;
|
|
45
14
|
const changed = applyLightweightPart(accumulator.lightweight, input.event, input.deltaSeq, input.timestamp ?? 0);
|
|
46
|
-
return { text: accumulator.lightweight.text, thinking: accumulator.lightweight.thinking, changed };
|
|
47
|
-
}
|
|
48
|
-
function reviveMessagePair(input) {
|
|
49
|
-
return {
|
|
50
|
-
userMessage: {
|
|
51
|
-
...input.userMessage,
|
|
52
|
-
createdAt: new Date(input.userMessage.createdAt),
|
|
53
|
-
updatedAt: input.userMessage.updatedAt ? new Date(input.userMessage.updatedAt) : null
|
|
54
|
-
},
|
|
55
|
-
assistantMessage: {
|
|
56
|
-
...input.assistantMessage,
|
|
57
|
-
createdAt: new Date(input.assistantMessage.createdAt),
|
|
58
|
-
updatedAt: input.assistantMessage.updatedAt ? new Date(input.assistantMessage.updatedAt) : null
|
|
59
|
-
}
|
|
60
|
-
};
|
|
15
|
+
return { text: accumulator.lightweight.text, thinking: accumulator.lightweight.thinking, changed, reasoning: accumulator.lightweight.reasoning, files: accumulator.lightweight.files };
|
|
61
16
|
}
|
|
62
17
|
export function useDirectStream(options = {}) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const streamingReasoning = ref([]);
|
|
68
|
-
const streamingFiles = ref([]);
|
|
69
|
-
let accumulator = createMessagePartsAccumulator();
|
|
70
|
-
let abortController = null;
|
|
71
|
-
function resetAccumulator(runId = null) {
|
|
72
|
-
accumulator = createMessagePartsAccumulator(runId);
|
|
73
|
-
activeRunId.value = runId;
|
|
74
|
-
streamingText.value = null;
|
|
75
|
-
streamingThinking.value = "";
|
|
76
|
-
streamingReasoning.value = [];
|
|
77
|
-
streamingFiles.value = [];
|
|
78
|
-
}
|
|
79
|
-
function beginMessage() {
|
|
80
|
-
resetAccumulator(activeRunId.value);
|
|
81
|
-
activity.value = "idle";
|
|
82
|
-
}
|
|
83
|
-
function disconnect(reset = true) {
|
|
84
|
-
abortController?.abort();
|
|
85
|
-
abortController = null;
|
|
86
|
-
activity.value = "idle";
|
|
87
|
-
if (reset)
|
|
88
|
-
resetAccumulator();
|
|
89
|
-
}
|
|
90
|
-
async function connectStream(url, init, onAccepted, expectedAssistantMessageId) {
|
|
91
|
-
if (import.meta.server)
|
|
92
|
-
throw new Error("Message streaming is unavailable during server rendering");
|
|
93
|
-
abortController?.abort();
|
|
94
|
-
const controller = new AbortController();
|
|
95
|
-
abortController = controller;
|
|
96
|
-
let response;
|
|
97
|
-
try {
|
|
98
|
-
response = await fetch(url, { ...init, signal: controller.signal });
|
|
99
|
-
} catch (error) {
|
|
100
|
-
if (abortController === controller)
|
|
101
|
-
abortController = null;
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
if (abortController === controller)
|
|
106
|
-
abortController = null;
|
|
107
|
-
const text = await response.text().catch(() => "");
|
|
108
|
-
let body = text;
|
|
109
|
-
if (text) {
|
|
110
|
-
try {
|
|
111
|
-
body = JSON.parse(text);
|
|
112
|
-
} catch {
|
|
113
|
-
body = { error: text };
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
throw new DirectStreamHttpError(response.status, body, getChatApiErrorCode(body));
|
|
117
|
-
}
|
|
118
|
-
if (!response.body) {
|
|
119
|
-
if (abortController === controller)
|
|
120
|
-
abortController = null;
|
|
121
|
-
throw new Error("Message stream response has no body");
|
|
122
|
-
}
|
|
123
|
-
if (controller.signal.aborted)
|
|
124
|
-
throw new DOMException("Aborted", "AbortError");
|
|
125
|
-
activity.value = "connected";
|
|
126
|
-
let resolveAccepted;
|
|
127
|
-
let rejectAccepted;
|
|
128
|
-
const acceptedPromise = new Promise((resolve, reject) => {
|
|
129
|
-
resolveAccepted = resolve;
|
|
130
|
-
rejectAccepted = reject;
|
|
131
|
-
});
|
|
132
|
-
let accepted = !onAccepted;
|
|
133
|
-
if (accepted)
|
|
134
|
-
resolveAccepted();
|
|
135
|
-
let optimisticResultReported = false;
|
|
136
|
-
let terminalEventObserved = false;
|
|
137
|
-
let terminalReported = false;
|
|
138
|
-
let recoveryRequested = false;
|
|
139
|
-
function requestRecovery() {
|
|
140
|
-
if (recoveryRequested)
|
|
141
|
-
return;
|
|
142
|
-
recoveryRequested = true;
|
|
143
|
-
options.onDisconnect?.();
|
|
144
|
-
}
|
|
145
|
-
function reportTerminal() {
|
|
146
|
-
if (terminalReported)
|
|
147
|
-
return;
|
|
148
|
-
terminalReported = true;
|
|
149
|
-
recoveryRequested = true;
|
|
150
|
-
options.onTerminal?.();
|
|
151
|
-
}
|
|
152
|
-
function reportOptimisticResult(status) {
|
|
153
|
-
if (status === "completed" || status === "waiting_messages") {
|
|
154
|
-
const result = {
|
|
155
|
-
status,
|
|
156
|
-
text: streamingText.value ?? ""
|
|
157
|
-
};
|
|
158
|
-
if (!optimisticResultReported) {
|
|
159
|
-
optimisticResultReported = true;
|
|
160
|
-
options.onOptimisticResult?.(result);
|
|
161
|
-
}
|
|
162
|
-
return result;
|
|
163
|
-
}
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
function settleResult(status) {
|
|
167
|
-
if (terminalEventObserved)
|
|
168
|
-
return;
|
|
169
|
-
terminalEventObserved = true;
|
|
170
|
-
const result = reportOptimisticResult(status);
|
|
171
|
-
if (result)
|
|
172
|
-
options.onResult?.(result);
|
|
173
|
-
reportTerminal();
|
|
174
|
-
}
|
|
175
|
-
function settleTerminal() {
|
|
176
|
-
if (terminalEventObserved)
|
|
177
|
-
return;
|
|
178
|
-
terminalEventObserved = true;
|
|
179
|
-
reportTerminal();
|
|
180
|
-
}
|
|
181
|
-
void (async () => {
|
|
182
|
-
try {
|
|
183
|
-
const reader = response.body.getReader();
|
|
184
|
-
const decoder = new TextDecoder();
|
|
185
|
-
let buffered = "";
|
|
186
|
-
while (!controller.signal.aborted) {
|
|
187
|
-
const { value, done } = await reader.read();
|
|
188
|
-
if (done)
|
|
189
|
-
break;
|
|
190
|
-
if (controller.signal.aborted)
|
|
191
|
-
break;
|
|
192
|
-
buffered += decoder.decode(value, { stream: true });
|
|
193
|
-
const parsed = parseSseFrames(buffered);
|
|
194
|
-
buffered = parsed.rest;
|
|
195
|
-
for (const frame of parsed.frames) {
|
|
196
|
-
if (controller.signal.aborted)
|
|
197
|
-
break;
|
|
198
|
-
if (frame.data === "[DONE]")
|
|
199
|
-
continue;
|
|
200
|
-
if (frame.event === "message-accepted") {
|
|
201
|
-
if (!onAccepted)
|
|
202
|
-
continue;
|
|
203
|
-
try {
|
|
204
|
-
const messagePair = reviveMessagePair(JSON.parse(frame.data));
|
|
205
|
-
accepted = true;
|
|
206
|
-
onAccepted(messagePair);
|
|
207
|
-
resolveAccepted(messagePair);
|
|
208
|
-
} catch (error) {
|
|
209
|
-
rejectAccepted(error);
|
|
210
|
-
}
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
let event;
|
|
214
|
-
try {
|
|
215
|
-
event = JSON.parse(frame.data);
|
|
216
|
-
} catch {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
if (!isRecord(event) || typeof event.kind !== "string")
|
|
220
|
-
continue;
|
|
221
|
-
if (event.kind === "conversation-state" && expectedAssistantMessageId && event.assistantMessageId !== expectedAssistantMessageId) {
|
|
222
|
-
controller.abort();
|
|
223
|
-
requestRecovery();
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
activity.value = "active";
|
|
227
|
-
if (event.kind === "result") {
|
|
228
|
-
settleResult(event.status);
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (event.kind === "steps" && (event.status === "completed" || event.status === "waiting_messages")) {
|
|
232
|
-
reportOptimisticResult(event.status);
|
|
233
|
-
reportTerminal();
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
if (event.kind === "error") {
|
|
237
|
-
if (event.recoverable === true) {
|
|
238
|
-
requestRecovery();
|
|
239
|
-
} else {
|
|
240
|
-
settleTerminal();
|
|
241
|
-
}
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
if (event.kind === "status" && (event.status === "failed" || event.status === "cancelled")) {
|
|
245
|
-
settleTerminal();
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
if (event.kind !== "message-parts" || typeof event.runId !== "string" || typeof event.deltaSeq !== "number" || !Number.isInteger(event.deltaSeq) || event.deltaSeq < 0 || !isRecord(event.event)) {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
const current = applyMessagePartsEvent(accumulator, event);
|
|
252
|
-
activeRunId.value = accumulator.runId;
|
|
253
|
-
if (current.changed) {
|
|
254
|
-
streamingText.value = current.text;
|
|
255
|
-
streamingThinking.value = current.thinking;
|
|
256
|
-
if (accumulator.lightweight) {
|
|
257
|
-
streamingReasoning.value = [...accumulator.lightweight.reasoning];
|
|
258
|
-
streamingFiles.value = [...accumulator.lightweight.files];
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
if (!accepted)
|
|
264
|
-
rejectAccepted(new Error("Message stream ended before acknowledgement"));
|
|
265
|
-
} catch (error) {
|
|
266
|
-
if (!accepted)
|
|
267
|
-
rejectAccepted(error);
|
|
268
|
-
} finally {
|
|
269
|
-
if (abortController === controller) {
|
|
270
|
-
abortController = null;
|
|
271
|
-
if (!onAccepted)
|
|
272
|
-
activity.value = "idle";
|
|
273
|
-
}
|
|
274
|
-
if (accepted && !terminalEventObserved && !controller.signal.aborted)
|
|
275
|
-
requestRecovery();
|
|
276
|
-
}
|
|
277
|
-
})();
|
|
278
|
-
return acceptedPromise;
|
|
279
|
-
}
|
|
280
|
-
onUnmounted(() => disconnect());
|
|
281
|
-
return {
|
|
282
|
-
activity: readonly(activity),
|
|
283
|
-
streamingText: readonly(streamingText),
|
|
284
|
-
streamingThinking: readonly(streamingThinking),
|
|
285
|
-
activeRunId: readonly(activeRunId),
|
|
286
|
-
streamingReasoning: readonly(streamingReasoning),
|
|
287
|
-
streamingFiles: readonly(streamingFiles),
|
|
288
|
-
connectMessage: (url, init, onAccepted) => connectStream(url, init, onAccepted),
|
|
289
|
-
connectConversation: (url, init, expectedAssistantMessageId) => connectStream(url, { ...init, method: "GET" }, void 0, expectedAssistantMessageId),
|
|
290
|
-
beginMessage,
|
|
291
|
-
disconnect
|
|
292
|
-
};
|
|
18
|
+
return useDirectStreamWithProtocol({
|
|
19
|
+
create: createMessagePartsAccumulator,
|
|
20
|
+
apply: applyMessagePartsEvent
|
|
21
|
+
}, options);
|
|
293
22
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { resolveAvailableChatModels } from "
|
|
1
|
+
import { resolveAvailableChatModels } from "../../messages/model-availability.js";
|
|
2
2
|
export function buildChatModelOptions(input) {
|
|
3
3
|
if (input.models.length === 0)
|
|
4
4
|
return [];
|
|
5
5
|
const availableModels = resolveAvailableChatModels(
|
|
6
|
-
input.
|
|
7
|
-
input.
|
|
6
|
+
input.availableModels,
|
|
7
|
+
input.models.map((candidate) => candidate.id)
|
|
8
8
|
);
|
|
9
9
|
const options = availableModels.map((modelId) => {
|
|
10
10
|
const model = input.models.find((candidate) => candidate.id === modelId);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ReasoningItem } from '../../types/schemas/integrations/agent-api.js';
|
|
2
|
+
import type { MessageFile } from '../../types/schemas/chat/files.js';
|
|
1
3
|
import type { SendMessageResponse } from '../../types/schemas/chat/messages.js';
|
|
2
4
|
export type DirectStreamActivity = 'idle' | 'connected' | 'active';
|
|
3
5
|
export type DirectStreamResultStatus = 'completed' | 'waiting_messages';
|
|
@@ -11,6 +13,9 @@ export type SseFrame = {
|
|
|
11
13
|
};
|
|
12
14
|
export type MessagePartsEvent = {
|
|
13
15
|
kind: 'message-parts';
|
|
16
|
+
harness?: string;
|
|
17
|
+
sessionId?: string;
|
|
18
|
+
timestamp?: number;
|
|
14
19
|
runId: string;
|
|
15
20
|
deltaSeq: number;
|
|
16
21
|
parentToolUseId?: string | null;
|
|
@@ -32,18 +37,46 @@ export declare function applyMessagePartsEvent(accumulator: MessagePartsAccumula
|
|
|
32
37
|
thinking: string;
|
|
33
38
|
changed: boolean;
|
|
34
39
|
};
|
|
35
|
-
export
|
|
40
|
+
export type DirectStreamOptions = {
|
|
36
41
|
onOptimisticResult?: (result: DirectStreamResult) => void;
|
|
37
42
|
onResult?: (result: DirectStreamResult) => void;
|
|
38
43
|
onTerminal?: () => void;
|
|
39
44
|
onDisconnect?: () => void;
|
|
40
|
-
}
|
|
45
|
+
};
|
|
46
|
+
type MessagePartsUpdate = {
|
|
47
|
+
text: string;
|
|
48
|
+
thinking: string;
|
|
49
|
+
changed: boolean;
|
|
50
|
+
reasoning?: ReasoningItem[];
|
|
51
|
+
files?: MessageFile[];
|
|
52
|
+
};
|
|
53
|
+
export declare function useDirectStream(options?: DirectStreamOptions): {
|
|
54
|
+
activity: any;
|
|
55
|
+
streamingText: any;
|
|
56
|
+
streamingThinking: any;
|
|
57
|
+
activeRunId: any;
|
|
58
|
+
streamingReasoning: any;
|
|
59
|
+
streamingFiles: any;
|
|
60
|
+
connectMessage: (url: string, init: RequestInit, onAccepted: (response: SendMessageResponse) => void) => Promise<SendMessageResponse>;
|
|
61
|
+
connectConversation: (url: string, init: RequestInit, expectedAssistantMessageId?: string) => Promise<void>;
|
|
62
|
+
beginMessage: () => void;
|
|
63
|
+
disconnect: (reset?: boolean) => void;
|
|
64
|
+
};
|
|
65
|
+
export declare function useDirectStreamWithProtocol<T extends {
|
|
66
|
+
runId: string | null;
|
|
67
|
+
}>(protocol: {
|
|
68
|
+
create: (runId?: string | null) => T;
|
|
69
|
+
apply: (accumulator: T, event: MessagePartsEvent) => MessagePartsUpdate;
|
|
70
|
+
}, options?: DirectStreamOptions): {
|
|
41
71
|
activity: any;
|
|
42
72
|
streamingText: any;
|
|
43
73
|
streamingThinking: any;
|
|
44
74
|
activeRunId: any;
|
|
75
|
+
streamingReasoning: any;
|
|
76
|
+
streamingFiles: any;
|
|
45
77
|
connectMessage: (url: string, init: RequestInit, onAccepted: (response: SendMessageResponse) => void) => Promise<SendMessageResponse>;
|
|
46
78
|
connectConversation: (url: string, init: RequestInit, expectedAssistantMessageId?: string) => Promise<void>;
|
|
47
79
|
beginMessage: () => void;
|
|
48
80
|
disconnect: (reset?: boolean) => void;
|
|
49
81
|
};
|
|
82
|
+
export {};
|
|
@@ -114,17 +114,27 @@ function reviveMessagePair(input) {
|
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
116
|
export function useDirectStream(options = {}) {
|
|
117
|
+
return useDirectStreamWithProtocol({
|
|
118
|
+
create: createMessagePartsAccumulator,
|
|
119
|
+
apply: applyMessagePartsEvent
|
|
120
|
+
}, options);
|
|
121
|
+
}
|
|
122
|
+
export function useDirectStreamWithProtocol(protocol, options = {}) {
|
|
117
123
|
const activity = ref("idle");
|
|
118
124
|
const streamingText = ref(null);
|
|
119
125
|
const streamingThinking = ref("");
|
|
120
126
|
const activeRunId = ref(null);
|
|
121
|
-
|
|
127
|
+
const streamingReasoning = ref([]);
|
|
128
|
+
const streamingFiles = ref([]);
|
|
129
|
+
let accumulator = protocol.create();
|
|
122
130
|
let abortController = null;
|
|
123
131
|
function resetAccumulator(runId = null) {
|
|
124
|
-
accumulator =
|
|
132
|
+
accumulator = protocol.create(runId);
|
|
125
133
|
activeRunId.value = runId;
|
|
126
134
|
streamingText.value = null;
|
|
127
135
|
streamingThinking.value = "";
|
|
136
|
+
streamingReasoning.value = [];
|
|
137
|
+
streamingFiles.value = [];
|
|
128
138
|
}
|
|
129
139
|
function beginMessage() {
|
|
130
140
|
resetAccumulator(activeRunId.value);
|
|
@@ -298,11 +308,13 @@ export function useDirectStream(options = {}) {
|
|
|
298
308
|
if (event.kind !== "message-parts" || typeof event.runId !== "string" || typeof event.deltaSeq !== "number" || !Number.isInteger(event.deltaSeq) || event.deltaSeq < 0 || !isRecord(event.event)) {
|
|
299
309
|
continue;
|
|
300
310
|
}
|
|
301
|
-
const current =
|
|
311
|
+
const current = protocol.apply(accumulator, event);
|
|
302
312
|
activeRunId.value = accumulator.runId;
|
|
303
313
|
if (current.changed) {
|
|
304
314
|
streamingText.value = current.text;
|
|
305
315
|
streamingThinking.value = current.thinking;
|
|
316
|
+
streamingReasoning.value = [...current.reasoning ?? []];
|
|
317
|
+
streamingFiles.value = [...current.files ?? []];
|
|
306
318
|
}
|
|
307
319
|
}
|
|
308
320
|
}
|
|
@@ -329,6 +341,8 @@ export function useDirectStream(options = {}) {
|
|
|
329
341
|
streamingText: readonly(streamingText),
|
|
330
342
|
streamingThinking: readonly(streamingThinking),
|
|
331
343
|
activeRunId: readonly(activeRunId),
|
|
344
|
+
streamingReasoning: readonly(streamingReasoning),
|
|
345
|
+
streamingFiles: readonly(streamingFiles),
|
|
332
346
|
connectMessage: (url, init, onAccepted) => connectStream(url, init, onAccepted),
|
|
333
347
|
connectConversation: (url, init, expectedAssistantMessageId) => connectStream(url, { ...init, method: "GET" }, void 0, expectedAssistantMessageId),
|
|
334
348
|
beginMessage,
|
|
@@ -1,6 +1,8 @@
|
|
|
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";
|
|
4
5
|
export declare function resolveAvailableChatModels(availableModels?: readonly string[]): readonly ChatModel[];
|
|
6
|
+
export declare function resolveAvailableChatModels(availableModels: readonly string[] | undefined, catalogModels: readonly string[]): readonly string[];
|
|
5
7
|
export declare function resolveChatModelSelection(selectedModel: string | null, availableModels?: readonly string[]): string | null;
|
|
6
|
-
export declare function ensureConversationUsesSelectedModel(conversationModel: string | null, selectedModel: string | null, updateModel: (model: string | null) => Promise<boolean
|
|
8
|
+
export declare function ensureConversationUsesSelectedModel(conversationModel: string | null, selectedModel: string | null, updateModel: (model: string | null) => Promise<boolean>, availableModels?: readonly string[]): Promise<void>;
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { chatModelIds
|
|
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
|
|
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");
|
|
4
8
|
if (availableModels === void 0)
|
|
5
|
-
return
|
|
9
|
+
return [...new Set(catalog)];
|
|
6
10
|
if (availableModels.length === 0)
|
|
7
11
|
throw new Error("availableModels must contain at least one model");
|
|
8
12
|
const uniqueModels = [];
|
|
9
13
|
for (const model of availableModels) {
|
|
10
|
-
if (!
|
|
11
|
-
throw new Error(`${model} is not a supported chat model`);
|
|
14
|
+
if (!catalog.includes(model)) {
|
|
15
|
+
throw new Error(catalogModels ? `${model} is not available in this workspace` : `${model} is not a supported chat model`);
|
|
16
|
+
}
|
|
12
17
|
if (!uniqueModels.includes(model))
|
|
13
18
|
uniqueModels.push(model);
|
|
14
19
|
}
|
|
@@ -20,7 +25,9 @@ export function resolveChatModelSelection(selectedModel, availableModels) {
|
|
|
20
25
|
const resolvedModels = resolveAvailableChatModels(availableModels);
|
|
21
26
|
return selectedModel && resolvedModels.includes(selectedModel) ? selectedModel : resolvedModels[0];
|
|
22
27
|
}
|
|
23
|
-
export async function ensureConversationUsesSelectedModel(conversationModel, selectedModel, updateModel) {
|
|
28
|
+
export async function ensureConversationUsesSelectedModel(conversationModel, selectedModel, updateModel, availableModels) {
|
|
29
|
+
if (selectedModel && availableModels && !availableModels.includes(selectedModel))
|
|
30
|
+
throw new Error(MODEL_UNAVAILABLE_MESSAGE);
|
|
24
31
|
if (conversationModel === selectedModel)
|
|
25
32
|
return;
|
|
26
33
|
if (!await updateModel(selectedModel))
|
|
@@ -1,14 +1,14 @@
|
|
|
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
|
-
harness?: Ref<ChatHarness>;
|
|
4
|
+
harness?: Ref<ChatHarness | null>;
|
|
5
5
|
workspaceId: Ref<string>;
|
|
6
6
|
telaAgentId: Ref<string | null>;
|
|
7
7
|
telaAgentBranch: Ref<string | null>;
|
|
8
8
|
conversationScope: Ref<string | null>;
|
|
9
9
|
};
|
|
10
10
|
type ChatRequestContextInput = {
|
|
11
|
-
harness?: MaybeRefOrGetter<ChatHarness>;
|
|
11
|
+
harness?: MaybeRefOrGetter<ChatHarness | null>;
|
|
12
12
|
workspaceId: MaybeRefOrGetter<string | null | undefined>;
|
|
13
13
|
telaAgentId?: MaybeRefOrGetter<string | null | undefined>;
|
|
14
14
|
telaAgentBranch?: MaybeRefOrGetter<string | null | undefined>;
|
|
@@ -5,7 +5,7 @@ function normalizeOptionalHeaderValue(value) {
|
|
|
5
5
|
}
|
|
6
6
|
function useGlobalChatRequestContextState() {
|
|
7
7
|
return {
|
|
8
|
-
harness: useState("chat-request-context-harness", () =>
|
|
8
|
+
harness: useState("chat-request-context-harness", () => null),
|
|
9
9
|
provided: useState("chat-request-context-provided", () => false),
|
|
10
10
|
workspaceId: useState("chat-request-context-workspace-id", () => ""),
|
|
11
11
|
telaAgentId: useState("chat-request-context-tela-agent-id", () => null),
|
|
@@ -15,7 +15,7 @@ function useGlobalChatRequestContextState() {
|
|
|
15
15
|
}
|
|
16
16
|
export function provideChatRequestContext(input, options = {}) {
|
|
17
17
|
const context = {
|
|
18
|
-
harness: computed(() => toValue(input.harness) ??
|
|
18
|
+
harness: computed(() => toValue(input.harness) ?? null),
|
|
19
19
|
workspaceId: computed(() => normalizeOptionalHeaderValue(toValue(input.workspaceId)) ?? ""),
|
|
20
20
|
telaAgentId: computed(() => normalizeOptionalHeaderValue(toValue(input.telaAgentId))),
|
|
21
21
|
telaAgentBranch: computed(() => normalizeOptionalHeaderValue(toValue(input.telaAgentBranch))),
|
package/package.json
CHANGED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { ChatModel } from '../../types/schemas/chat/lightweight-models.js';
|
|
2
|
-
export type AvailableChatModels = readonly [ChatModel, ...ChatModel[]];
|
|
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(catalogModels: readonly ChatModel[], availableModels?: readonly ChatModel[]): readonly ChatModel[];
|
|
6
|
-
export declare function resolveChatModelSelection(selectedModel: string | null, defaultModel: string | null): string | null;
|
|
7
|
-
export declare function ensureConversationUsesSelectedModel(conversationModel: string | null, selectedModel: string | null, updateModel: (model: string | null) => Promise<boolean>, availableModels?: readonly string[]): Promise<void>;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export const MODEL_UPDATE_FAILED_MESSAGE = "N\xE3o foi poss\xEDvel atualizar o modelo da conversa";
|
|
2
|
-
export const MODEL_UNAVAILABLE_MESSAGE = "O modelo selecionado n\xE3o est\xE1 dispon\xEDvel neste workspace";
|
|
3
|
-
export function resolveAvailableChatModels(catalogModels, availableModels) {
|
|
4
|
-
if (catalogModels.length === 0)
|
|
5
|
-
throw new Error("Agent API model catalog must contain at least one model");
|
|
6
|
-
if (availableModels === void 0)
|
|
7
|
-
return [...new Set(catalogModels)];
|
|
8
|
-
if (availableModels.length === 0)
|
|
9
|
-
throw new Error("availableModels must contain at least one model");
|
|
10
|
-
const uniqueModels = [];
|
|
11
|
-
for (const model of availableModels) {
|
|
12
|
-
if (!catalogModels.includes(model))
|
|
13
|
-
throw new Error(`${model} is not available in this workspace`);
|
|
14
|
-
if (!uniqueModels.includes(model))
|
|
15
|
-
uniqueModels.push(model);
|
|
16
|
-
}
|
|
17
|
-
return uniqueModels;
|
|
18
|
-
}
|
|
19
|
-
export function resolveChatModelSelection(selectedModel, defaultModel) {
|
|
20
|
-
return selectedModel ?? defaultModel;
|
|
21
|
-
}
|
|
22
|
-
export async function ensureConversationUsesSelectedModel(conversationModel, selectedModel, updateModel, availableModels) {
|
|
23
|
-
if (selectedModel && availableModels && !availableModels.includes(selectedModel))
|
|
24
|
-
throw new Error(MODEL_UNAVAILABLE_MESSAGE);
|
|
25
|
-
if (conversationModel === selectedModel)
|
|
26
|
-
return;
|
|
27
|
-
if (!await updateModel(selectedModel))
|
|
28
|
-
throw new Error(MODEL_UPDATE_FAILED_MESSAGE);
|
|
29
|
-
}
|