@tuturuuu/ai 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +41 -41
- package/src/chat/google/assistant-message-parts.ts +105 -0
- package/src/chat/google/chat-tools.ts +7 -0
- package/src/chat/google/google-search-usage.ts +62 -0
- package/src/chat/google/mira-step-preparation.ts +37 -16
- package/src/chat/google/route-credits.ts +6 -7
- package/src/chat/google/route-message-preparation.ts +33 -0
- package/src/chat/google/route-mira-runtime.ts +56 -32
- package/src/chat/google/route.ts +32 -71
- package/src/chat/google/stream-finish-persistence.ts +39 -150
- package/src/chat/mira-artifact-intent.ts +38 -0
- package/src/chat/mira-prompt.ts +29 -0
- package/src/chat/mira-render-ui-policy.ts +65 -20
- package/src/chat/mira-system-instruction.ts +8 -172
- package/src/chat/mira-tool-loop-guard.ts +84 -0
- package/src/credits/cap-output-tokens.ts +48 -15
- package/src/credits/check-credits.ts +14 -3
- package/src/credits/constants.ts +3 -0
- package/src/credits/index.ts +1 -0
- package/src/generate/route.ts +6 -3
- package/src/meetings/audio-contract.ts +8 -0
- package/src/meetings/audio-transcripts.ts +25 -0
- package/src/meetings/chat-source-footer.ts +1 -0
- package/src/meetings/chat.ts +4 -1
- package/src/meetings/gemini.ts +72 -20
- package/src/meetings/public-chat-search.ts +4 -1
- package/src/meetings/transcript.ts +63 -0
- package/src/meetings/workspace-tools.ts +3 -1
- package/src/studio/metering.ts +22 -3
- package/src/tools/definitions/calendar-automation.ts +43 -0
- package/src/tools/definitions/calendar.ts +27 -1
- package/src/tools/definitions/meta.ts +17 -1
- package/src/tools/definitions/tasks.ts +82 -31
- package/src/tools/definitions/workspace-user-chat.ts +24 -0
- package/src/tools/executors/calendar-automation.ts +207 -0
- package/src/tools/executors/calendar.ts +30 -1
- package/src/tools/executors/helpers/encryption.ts +12 -9
- package/src/tools/executors/search.ts +40 -9
- package/src/tools/executors/task-create.ts +232 -0
- package/src/tools/executors/task-dashboard.ts +59 -0
- package/src/tools/executors/task-read.ts +76 -0
- package/src/tools/executors/tasks.ts +50 -205
- package/src/tools/executors/theme.ts +48 -0
- package/src/tools/google-search-events.ts +4 -0
- package/src/tools/mira-sdk-tool-search.ts +73 -0
- package/src/tools/mira-task-write-guard.ts +61 -0
- package/src/tools/mira-tool-discovery.ts +138 -0
- package/src/tools/mira-tool-dispatcher.ts +33 -1
- package/src/tools/mira-tool-metadata.ts +29 -1
- package/src/tools/mira-tool-names.ts +13 -0
- package/src/tools/mira-tool-types.ts +7 -0
- package/src/tools/mira-tools.ts +70 -2
- package/src/workspace-artifacts.ts +58 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuturuuu/ai",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/tutur3u/platform",
|
|
@@ -22,34 +22,34 @@
|
|
|
22
22
|
"type-check": "tsc --project tsconfig.typecheck.json"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@ai-sdk/amazon-bedrock": "^5.0.
|
|
26
|
-
"@ai-sdk/anthropic": "^4.0.
|
|
27
|
-
"@ai-sdk/azure": "^4.0.
|
|
28
|
-
"@ai-sdk/cerebras": "^3.0.
|
|
29
|
-
"@ai-sdk/cohere": "^4.0.
|
|
30
|
-
"@ai-sdk/deepgram": "^3.1.
|
|
31
|
-
"@ai-sdk/deepinfra": "^3.0.
|
|
32
|
-
"@ai-sdk/deepseek": "^3.0.
|
|
33
|
-
"@ai-sdk/elevenlabs": "^3.0.
|
|
34
|
-
"@ai-sdk/fal": "^3.0.
|
|
35
|
-
"@ai-sdk/fireworks": "^3.0.
|
|
36
|
-
"@ai-sdk/gateway": "^4.0.
|
|
37
|
-
"@ai-sdk/gladia": "^3.0.
|
|
38
|
-
"@ai-sdk/google": "^4.0.
|
|
39
|
-
"@ai-sdk/google-vertex": "^5.0.
|
|
40
|
-
"@ai-sdk/groq": "^4.0.
|
|
41
|
-
"@ai-sdk/hume": "^3.0.
|
|
25
|
+
"@ai-sdk/amazon-bedrock": "^5.0.82",
|
|
26
|
+
"@ai-sdk/anthropic": "^4.0.53",
|
|
27
|
+
"@ai-sdk/azure": "^4.0.70",
|
|
28
|
+
"@ai-sdk/cerebras": "^3.0.48",
|
|
29
|
+
"@ai-sdk/cohere": "^4.0.41",
|
|
30
|
+
"@ai-sdk/deepgram": "^3.1.11",
|
|
31
|
+
"@ai-sdk/deepinfra": "^3.0.48",
|
|
32
|
+
"@ai-sdk/deepseek": "^3.0.44",
|
|
33
|
+
"@ai-sdk/elevenlabs": "^3.0.41",
|
|
34
|
+
"@ai-sdk/fal": "^3.0.41",
|
|
35
|
+
"@ai-sdk/fireworks": "^3.0.51",
|
|
36
|
+
"@ai-sdk/gateway": "^4.0.80",
|
|
37
|
+
"@ai-sdk/gladia": "^3.0.40",
|
|
38
|
+
"@ai-sdk/google": "^4.0.69",
|
|
39
|
+
"@ai-sdk/google-vertex": "^5.0.81",
|
|
40
|
+
"@ai-sdk/groq": "^4.0.41",
|
|
41
|
+
"@ai-sdk/hume": "^3.0.40",
|
|
42
42
|
"@ai-sdk/lmnt": "^3.0.36",
|
|
43
|
-
"@ai-sdk/luma": "^3.0.
|
|
44
|
-
"@ai-sdk/mistral": "^4.0.
|
|
45
|
-
"@ai-sdk/openai": "^4.0.
|
|
46
|
-
"@ai-sdk/openai-compatible": "^3.0.
|
|
47
|
-
"@ai-sdk/perplexity": "^4.0.
|
|
48
|
-
"@ai-sdk/react": "^4.0.
|
|
49
|
-
"@ai-sdk/replicate": "^3.0.
|
|
50
|
-
"@ai-sdk/revai": "^3.0.
|
|
51
|
-
"@ai-sdk/togetherai": "^3.0.
|
|
52
|
-
"@ai-sdk/xai": "^4.0.
|
|
43
|
+
"@ai-sdk/luma": "^3.0.41",
|
|
44
|
+
"@ai-sdk/mistral": "^4.0.43",
|
|
45
|
+
"@ai-sdk/openai": "^4.0.66",
|
|
46
|
+
"@ai-sdk/openai-compatible": "^3.0.48",
|
|
47
|
+
"@ai-sdk/perplexity": "^4.0.43",
|
|
48
|
+
"@ai-sdk/react": "^4.0.110",
|
|
49
|
+
"@ai-sdk/replicate": "^3.0.41",
|
|
50
|
+
"@ai-sdk/revai": "^3.0.40",
|
|
51
|
+
"@ai-sdk/togetherai": "^3.0.49",
|
|
52
|
+
"@ai-sdk/xai": "^4.0.58",
|
|
53
53
|
"@beeper/chat-adapter-matrix": "^0.2.0",
|
|
54
54
|
"@bitbasti/chat-adapter-webex": "^0.1.0",
|
|
55
55
|
"@chat-adapter/discord": "^4.40.0",
|
|
@@ -75,25 +75,25 @@
|
|
|
75
75
|
"@streamdown/code": "^1.1.1",
|
|
76
76
|
"@streamdown/math": "^1.0.2",
|
|
77
77
|
"@streamdown/mermaid": "^1.0.2",
|
|
78
|
-
"@tuturuuu/google": "0.1.
|
|
79
|
-
"@tuturuuu/internal-api": "0.
|
|
80
|
-
"@tuturuuu/supabase": "0.5.
|
|
81
|
-
"@tuturuuu/utils": "0.
|
|
82
|
-
"@vercel/sandbox": "^3.
|
|
78
|
+
"@tuturuuu/google": "0.1.1",
|
|
79
|
+
"@tuturuuu/internal-api": "0.39.0",
|
|
80
|
+
"@tuturuuu/supabase": "0.5.2",
|
|
81
|
+
"@tuturuuu/utils": "0.29.0",
|
|
82
|
+
"@vercel/sandbox": "^3.3.0",
|
|
83
83
|
"@zernio/chat-sdk-adapter": "^0.5.1",
|
|
84
|
-
"ai": "^7.0.
|
|
84
|
+
"ai": "^7.0.107",
|
|
85
85
|
"bash-tool": "^1.3.19",
|
|
86
86
|
"chat": "^4.40.0",
|
|
87
87
|
"chat-adapter-baileys": "^2.1.0",
|
|
88
88
|
"chat-adapter-blooio": "^0.1.0",
|
|
89
89
|
"chat-adapter-imessage": "^1.1.0",
|
|
90
|
-
"chat-adapter-mattermost": "^
|
|
90
|
+
"chat-adapter-mattermost": "^2.0.0",
|
|
91
91
|
"chat-adapter-sendblue": "^0.2.0",
|
|
92
92
|
"chat-adapter-zalo": "^0.1.0",
|
|
93
93
|
"chat-state-cloudflare-do": "^0.2.0",
|
|
94
94
|
"chat-state-mysql": "^0.1.0",
|
|
95
95
|
"dayjs": "^1.11.23",
|
|
96
|
-
"next": "^16.3.
|
|
96
|
+
"next": "^16.3.5",
|
|
97
97
|
"qrcode": "^1.5.4",
|
|
98
98
|
"react": "^19.2.8",
|
|
99
99
|
"react-dom": "^19.2.8",
|
|
@@ -101,15 +101,15 @@
|
|
|
101
101
|
"streamdown": "^2.6.0",
|
|
102
102
|
"unified": "^11.0.5",
|
|
103
103
|
"uuid": "^14.0.2",
|
|
104
|
-
"zca-js": "^2.
|
|
105
|
-
"zod": "^4.
|
|
104
|
+
"zca-js": "^2.2.0",
|
|
105
|
+
"zod": "^4.6.2"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@tuturuuu/types": "0.
|
|
108
|
+
"@tuturuuu/types": "0.31.0",
|
|
109
109
|
"@tuturuuu/typescript-config": "0.1.2",
|
|
110
|
-
"@types/node": "^26.5.
|
|
110
|
+
"@types/node": "^26.5.1",
|
|
111
111
|
"@types/qrcode": "^1.5.6",
|
|
112
|
-
"@types/react": "
|
|
112
|
+
"@types/react": "~19.2.18",
|
|
113
113
|
"typescript": "7.0.2"
|
|
114
114
|
},
|
|
115
115
|
"exports": {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
type Part = Record<string, unknown>;
|
|
4
|
+
type Step = {
|
|
5
|
+
content?: readonly unknown[];
|
|
6
|
+
text?: string;
|
|
7
|
+
reasoningText?: string;
|
|
8
|
+
toolCalls?: readonly Part[];
|
|
9
|
+
toolResults?: readonly Part[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Keep provider content order; tool results update their original call slot. */
|
|
13
|
+
export function collectAssistantMessageParts(response: {
|
|
14
|
+
steps?: readonly Step[];
|
|
15
|
+
text?: string;
|
|
16
|
+
reasoningText?: string;
|
|
17
|
+
sources?: readonly { sourceId?: string; url?: string; title?: string }[];
|
|
18
|
+
}): Part[] {
|
|
19
|
+
const parts: Part[] = [];
|
|
20
|
+
const calls = new Map<string, Part>();
|
|
21
|
+
const append = (value: unknown): void => {
|
|
22
|
+
if (!value || typeof value !== 'object') return;
|
|
23
|
+
const part = value as Part;
|
|
24
|
+
if (part.type === 'text' || part.type === 'reasoning') {
|
|
25
|
+
if (typeof part.text === 'string' && part.text.length)
|
|
26
|
+
parts.push({ ...part });
|
|
27
|
+
} else if (
|
|
28
|
+
part.type === 'tool-call' ||
|
|
29
|
+
part.type === 'tool-result' ||
|
|
30
|
+
part.type === 'tool-error'
|
|
31
|
+
) {
|
|
32
|
+
const id =
|
|
33
|
+
typeof part.toolCallId === 'string' ? part.toolCallId : randomUUID();
|
|
34
|
+
let call = calls.get(id);
|
|
35
|
+
if (!call) {
|
|
36
|
+
call = {
|
|
37
|
+
type: 'dynamic-tool',
|
|
38
|
+
toolName: part.toolName ?? 'tool',
|
|
39
|
+
toolCallId: id,
|
|
40
|
+
state: 'input-available',
|
|
41
|
+
};
|
|
42
|
+
calls.set(id, call);
|
|
43
|
+
parts.push(call);
|
|
44
|
+
}
|
|
45
|
+
if (part.type === 'tool-call') {
|
|
46
|
+
call.input = part.input ?? part.args ?? part.arguments ?? {};
|
|
47
|
+
} else if (part.type === 'tool-error') {
|
|
48
|
+
call.state = 'output-error';
|
|
49
|
+
call.errorText =
|
|
50
|
+
typeof part.error === 'string' ? part.error : 'Tool execution failed';
|
|
51
|
+
} else {
|
|
52
|
+
call.state = 'output-available';
|
|
53
|
+
call.output = part.output ?? part.result ?? null;
|
|
54
|
+
}
|
|
55
|
+
} else if (part.type === 'source' && part.sourceType === 'url') {
|
|
56
|
+
parts.push({ ...part, type: 'source-url' });
|
|
57
|
+
} else if (part.type === 'file') {
|
|
58
|
+
const file = part.file as
|
|
59
|
+
| { mediaType?: string; base64?: string }
|
|
60
|
+
| undefined;
|
|
61
|
+
if (file?.mediaType && file.base64)
|
|
62
|
+
parts.push({
|
|
63
|
+
type: 'file',
|
|
64
|
+
mediaType: file.mediaType,
|
|
65
|
+
url: `data:${file.mediaType};base64,${file.base64}`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
for (const step of response.steps ?? []) {
|
|
70
|
+
parts.push({ type: 'step-start' });
|
|
71
|
+
if (step.content?.length) {
|
|
72
|
+
step.content.forEach(append);
|
|
73
|
+
} else {
|
|
74
|
+
// Legacy callers have only per-step aggregates; never flatten across steps.
|
|
75
|
+
if (step.reasoningText)
|
|
76
|
+
append({ type: 'reasoning', text: step.reasoningText });
|
|
77
|
+
if (step.text) append({ type: 'text', text: step.text });
|
|
78
|
+
for (const part of step.toolCalls ?? [])
|
|
79
|
+
append({ ...part, type: 'tool-call' });
|
|
80
|
+
}
|
|
81
|
+
for (const part of step.toolResults ?? [])
|
|
82
|
+
append({ ...part, type: 'tool-result' });
|
|
83
|
+
}
|
|
84
|
+
if (!parts.some((part) => part.type === 'text') && response.text)
|
|
85
|
+
append({ type: 'text', text: response.text });
|
|
86
|
+
if (
|
|
87
|
+
!parts.some((part) => part.type === 'reasoning') &&
|
|
88
|
+
response.reasoningText
|
|
89
|
+
)
|
|
90
|
+
append({ type: 'reasoning', text: response.reasoningText });
|
|
91
|
+
for (const source of response.sources ?? []) {
|
|
92
|
+
if (
|
|
93
|
+
source.url &&
|
|
94
|
+
!parts.some(
|
|
95
|
+
(part) => part.type === 'source-url' && part.url === source.url
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
parts.push({
|
|
99
|
+
...source,
|
|
100
|
+
type: 'source-url',
|
|
101
|
+
sourceId: source.sourceId ?? source.url,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return parts;
|
|
105
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ToolSet } from 'ai';
|
|
2
|
+
import { createGoogleSearchToolSet } from '../../tools/google-search-tool';
|
|
3
|
+
|
|
4
|
+
/** Mira search must retain its executor and grounding validation boundary. */
|
|
5
|
+
export function resolveChatTools(miraTools?: ToolSet | null): ToolSet {
|
|
6
|
+
return miraTools ?? createGoogleSearchToolSet();
|
|
7
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isGoogleSearchToolName } from '../../tools/google-search-events';
|
|
2
|
+
|
|
3
|
+
type SearchStep = {
|
|
4
|
+
toolCalls?: ReadonlyArray<{ toolName?: string }>;
|
|
5
|
+
sources?: ReadonlyArray<unknown>;
|
|
6
|
+
providerMetadata?: {
|
|
7
|
+
google?: { groundingMetadata?: { webSearchQueries?: unknown } };
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Count provider search units, without counting top-level/step metadata twice. */
|
|
12
|
+
export function countGoogleSearchQueries(
|
|
13
|
+
model: string,
|
|
14
|
+
response: SearchStep & { steps?: SearchStep[] },
|
|
15
|
+
allToolCalls: ReadonlyArray<{ toolName?: string }>
|
|
16
|
+
): number {
|
|
17
|
+
const perQuery = /(?:^|\/)gemini-3(?:[.-]|$)/.test(model);
|
|
18
|
+
const legacyGemini = /(?:^|\/)gemini-[12](?:[.-]|$)/.test(model);
|
|
19
|
+
const countStep = (step: SearchStep): number | null => {
|
|
20
|
+
const raw =
|
|
21
|
+
step.providerMetadata?.google?.groundingMetadata?.webSearchQueries;
|
|
22
|
+
const queries = Array.isArray(raw)
|
|
23
|
+
? new Set(
|
|
24
|
+
raw
|
|
25
|
+
.filter(
|
|
26
|
+
(query): query is string =>
|
|
27
|
+
typeof query === 'string' && query.trim().length > 0
|
|
28
|
+
)
|
|
29
|
+
.map((query) => query.trim())
|
|
30
|
+
).size
|
|
31
|
+
: null;
|
|
32
|
+
if (perQuery && queries !== null) return queries;
|
|
33
|
+
const calls =
|
|
34
|
+
step.toolCalls?.filter((call) => isGoogleSearchToolName(call.toolName))
|
|
35
|
+
.length ?? 0;
|
|
36
|
+
const units = queries || calls || (step.sources?.length ? 1 : 0);
|
|
37
|
+
if (!units) return null;
|
|
38
|
+
return legacyGemini ? 1 : calls || units;
|
|
39
|
+
};
|
|
40
|
+
// Each SDK step is a separate provider prompt; Gemini 2.x uses one unit per
|
|
41
|
+
// grounded prompt, while Gemini 3 uses its distinct nonempty search queries.
|
|
42
|
+
let observedStep = false;
|
|
43
|
+
const steps =
|
|
44
|
+
response.steps?.reduce((total, step, index) => {
|
|
45
|
+
const finalMetadata =
|
|
46
|
+
response.providerMetadata?.google?.groundingMetadata?.webSearchQueries;
|
|
47
|
+
const stepMetadata =
|
|
48
|
+
step.providerMetadata?.google?.groundingMetadata?.webSearchQueries;
|
|
49
|
+
const effectiveStep =
|
|
50
|
+
index === response.steps!.length - 1 &&
|
|
51
|
+
!Array.isArray(stepMetadata) &&
|
|
52
|
+
Array.isArray(finalMetadata)
|
|
53
|
+
? { ...step, providerMetadata: response.providerMetadata }
|
|
54
|
+
: step;
|
|
55
|
+
const units = countStep(effectiveStep);
|
|
56
|
+
observedStep ||= units !== null;
|
|
57
|
+
return total + (units ?? 0);
|
|
58
|
+
}, 0) ?? 0;
|
|
59
|
+
return observedStep
|
|
60
|
+
? steps
|
|
61
|
+
: (countStep({ ...response, toolCalls: allToolCalls }) ?? 0);
|
|
62
|
+
}
|
|
@@ -3,17 +3,20 @@ import {
|
|
|
3
3
|
buildActiveToolsFromSelected,
|
|
4
4
|
countRenderUiAttemptsInSteps,
|
|
5
5
|
extractSelectedToolsFromSteps,
|
|
6
|
+
getWorkspaceArtifactProgress,
|
|
6
7
|
hasRenderableRenderUiInSteps,
|
|
7
8
|
hasSuccessfulWorkspaceContextResolutionInSteps,
|
|
8
9
|
hasToolCallInSteps,
|
|
9
10
|
removeWorkspaceDiscoveryTools,
|
|
10
11
|
wasToolEverSelectedInSteps,
|
|
11
12
|
} from '../mira-render-ui-policy';
|
|
13
|
+
import { getMiraToolLoopReason } from '../mira-tool-loop-guard';
|
|
12
14
|
|
|
13
15
|
export type PrepareMiraToolStepInput = {
|
|
14
16
|
steps: unknown[];
|
|
15
17
|
forceGoogleSearch: boolean;
|
|
16
18
|
forceRenderUi: boolean;
|
|
19
|
+
forceWorkspaceArtifact?: boolean;
|
|
17
20
|
needsParallelChecks: boolean;
|
|
18
21
|
needsWorkspaceContextResolution: boolean;
|
|
19
22
|
needsWorkspaceMembersTool: boolean;
|
|
@@ -24,26 +27,41 @@ export function prepareMiraToolStep({
|
|
|
24
27
|
steps,
|
|
25
28
|
forceGoogleSearch,
|
|
26
29
|
forceRenderUi,
|
|
30
|
+
forceWorkspaceArtifact,
|
|
27
31
|
needsParallelChecks,
|
|
28
32
|
needsWorkspaceContextResolution,
|
|
29
33
|
needsWorkspaceMembersTool,
|
|
30
34
|
preferMarkdownTables,
|
|
31
35
|
}: PrepareMiraToolStepInput): {
|
|
32
|
-
toolChoice?: 'required';
|
|
36
|
+
toolChoice?: 'required' | 'none';
|
|
33
37
|
activeTools: string[];
|
|
34
38
|
} {
|
|
39
|
+
if (forceWorkspaceArtifact) forceRenderUi = false;
|
|
40
|
+
const artifact = getWorkspaceArtifactProgress(steps);
|
|
41
|
+
if (forceWorkspaceArtifact && !artifact.completed && artifact.attempts >= 3)
|
|
42
|
+
return { toolChoice: 'none', activeTools: [] };
|
|
43
|
+
if (
|
|
44
|
+
forceWorkspaceArtifact &&
|
|
45
|
+
!artifact.completed &&
|
|
46
|
+
(!needsWorkspaceContextResolution ||
|
|
47
|
+
hasSuccessfulWorkspaceContextResolutionInSteps(steps))
|
|
48
|
+
) {
|
|
49
|
+
return { toolChoice: 'required', activeTools: ['show_workspace_artifact'] };
|
|
50
|
+
}
|
|
51
|
+
if (getMiraToolLoopReason(steps))
|
|
52
|
+
return { toolChoice: 'none', activeTools: [] };
|
|
35
53
|
if (steps.length === 0) {
|
|
36
54
|
if (forceGoogleSearch) {
|
|
37
55
|
return {
|
|
38
56
|
toolChoice: 'required',
|
|
39
|
-
activeTools: ['google_search'
|
|
57
|
+
activeTools: ['google_search'],
|
|
40
58
|
};
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
if (needsParallelChecks) {
|
|
44
62
|
return {
|
|
45
63
|
toolChoice: 'required',
|
|
46
|
-
activeTools: ['run_parallel_checks', 'select_tools'],
|
|
64
|
+
activeTools: ['run_parallel_checks', 'search_tools', 'select_tools'],
|
|
47
65
|
};
|
|
48
66
|
}
|
|
49
67
|
|
|
@@ -54,6 +72,7 @@ export function prepareMiraToolStep({
|
|
|
54
72
|
'list_accessible_workspaces',
|
|
55
73
|
'get_workspace_context',
|
|
56
74
|
'set_workspace_context',
|
|
75
|
+
'search_tools',
|
|
57
76
|
'select_tools',
|
|
58
77
|
],
|
|
59
78
|
};
|
|
@@ -65,13 +84,14 @@ export function prepareMiraToolStep({
|
|
|
65
84
|
activeTools: [
|
|
66
85
|
'get_workspace_context',
|
|
67
86
|
'list_workspace_members',
|
|
87
|
+
'search_tools',
|
|
68
88
|
'select_tools',
|
|
69
89
|
],
|
|
70
90
|
};
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
return {
|
|
74
|
-
activeTools: ['select_tools', 'no_action_needed'],
|
|
94
|
+
activeTools: ['search_tools', 'select_tools', 'no_action_needed'],
|
|
75
95
|
};
|
|
76
96
|
}
|
|
77
97
|
|
|
@@ -106,11 +126,17 @@ export function prepareMiraToolStep({
|
|
|
106
126
|
return {
|
|
107
127
|
toolChoice: 'required',
|
|
108
128
|
activeTools: hasListedAccessibleWorkspaces
|
|
109
|
-
? [
|
|
129
|
+
? [
|
|
130
|
+
'get_workspace_context',
|
|
131
|
+
'set_workspace_context',
|
|
132
|
+
'search_tools',
|
|
133
|
+
'select_tools',
|
|
134
|
+
]
|
|
110
135
|
: [
|
|
111
136
|
'list_accessible_workspaces',
|
|
112
137
|
'get_workspace_context',
|
|
113
138
|
'set_workspace_context',
|
|
139
|
+
'search_tools',
|
|
114
140
|
'select_tools',
|
|
115
141
|
],
|
|
116
142
|
};
|
|
@@ -131,6 +157,7 @@ export function prepareMiraToolStep({
|
|
|
131
157
|
'get_workspace_context',
|
|
132
158
|
'list_workspace_members',
|
|
133
159
|
...selected,
|
|
160
|
+
'search_tools',
|
|
134
161
|
'select_tools',
|
|
135
162
|
];
|
|
136
163
|
|
|
@@ -141,14 +168,7 @@ export function prepareMiraToolStep({
|
|
|
141
168
|
}
|
|
142
169
|
|
|
143
170
|
if (forceGoogleSearch && !hasToolCallInSteps(steps, 'google_search')) {
|
|
144
|
-
|
|
145
|
-
.filter((toolName) => toolName !== 'no_action_needed')
|
|
146
|
-
.concat('google_search', 'select_tools');
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
toolChoice: 'required',
|
|
150
|
-
activeTools: Array.from(new Set(active)),
|
|
151
|
-
};
|
|
171
|
+
return { toolChoice: 'required', activeTools: ['google_search'] };
|
|
152
172
|
}
|
|
153
173
|
|
|
154
174
|
if (
|
|
@@ -157,7 +177,7 @@ export function prepareMiraToolStep({
|
|
|
157
177
|
) {
|
|
158
178
|
const active = buildActiveToolsFromSelected(toolsForBuild)
|
|
159
179
|
.filter((toolName) => toolName !== 'no_action_needed')
|
|
160
|
-
.concat('run_parallel_checks', 'select_tools');
|
|
180
|
+
.concat('run_parallel_checks', 'search_tools', 'select_tools');
|
|
161
181
|
|
|
162
182
|
return {
|
|
163
183
|
toolChoice: 'required',
|
|
@@ -178,6 +198,7 @@ export function prepareMiraToolStep({
|
|
|
178
198
|
toolName !== 'select_tools' && toolName !== 'no_action_needed'
|
|
179
199
|
),
|
|
180
200
|
'render_ui',
|
|
201
|
+
'search_tools',
|
|
181
202
|
'select_tools',
|
|
182
203
|
];
|
|
183
204
|
|
|
@@ -199,7 +220,7 @@ export function prepareMiraToolStep({
|
|
|
199
220
|
) {
|
|
200
221
|
const active = buildActiveToolsFromSelected(toolsForBuild)
|
|
201
222
|
.filter((toolName) => toolName !== 'no_action_needed')
|
|
202
|
-
.concat('render_ui', 'select_tools');
|
|
223
|
+
.concat('render_ui', 'search_tools', 'select_tools');
|
|
203
224
|
return {
|
|
204
225
|
toolChoice: 'required',
|
|
205
226
|
activeTools: Array.from(new Set(active)),
|
|
@@ -209,7 +230,7 @@ export function prepareMiraToolStep({
|
|
|
209
230
|
if (hasRenderableRenderUiInSteps(steps)) {
|
|
210
231
|
const active = buildActiveToolsFromSelected(toolsForBuild)
|
|
211
232
|
.filter((toolName) => toolName !== 'render_ui')
|
|
212
|
-
.concat('select_tools');
|
|
233
|
+
.concat('search_tools', 'select_tools');
|
|
213
234
|
return {
|
|
214
235
|
activeTools: Array.from(new Set(active)),
|
|
215
236
|
};
|
|
@@ -44,15 +44,14 @@ export async function performCreditPreflight({
|
|
|
44
44
|
)
|
|
45
45
|
: null;
|
|
46
46
|
|
|
47
|
-
if (
|
|
48
|
-
cappedMaxOutput === null &&
|
|
49
|
-
creditCheck &&
|
|
50
|
-
creditCheck.remainingCredits <= 0
|
|
51
|
-
) {
|
|
47
|
+
if (cappedMaxOutput === null) {
|
|
52
48
|
return {
|
|
53
49
|
error: Response.json(
|
|
54
|
-
{
|
|
55
|
-
|
|
50
|
+
{
|
|
51
|
+
error: 'AI request budget could not be verified',
|
|
52
|
+
code: 'CREDIT_CHECK_FAILED',
|
|
53
|
+
},
|
|
54
|
+
{ status: 503 }
|
|
56
55
|
),
|
|
57
56
|
};
|
|
58
57
|
}
|
|
@@ -377,3 +377,36 @@ export async function persistLatestUserMessage({
|
|
|
377
377
|
console.log('User message saved to database');
|
|
378
378
|
return null;
|
|
379
379
|
}
|
|
380
|
+
|
|
381
|
+
export function splitSystemMessages(messages: ModelMessage[]) {
|
|
382
|
+
const systemMessages: string[] = [];
|
|
383
|
+
const nonSystemMessages: ModelMessage[] = [];
|
|
384
|
+
for (const message of messages) {
|
|
385
|
+
if (message.role === 'system') {
|
|
386
|
+
const systemMessage = getTextContent(message.content);
|
|
387
|
+
if (systemMessage) systemMessages.push(systemMessage);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
nonSystemMessages.push(message);
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
messages: nonSystemMessages,
|
|
394
|
+
system: systemMessages.join('\n\n').trim(),
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
function getTextContent(content: ModelMessage['content']) {
|
|
398
|
+
if (typeof content === 'string') return content.trim();
|
|
399
|
+
if (!Array.isArray(content)) return '';
|
|
400
|
+
return content
|
|
401
|
+
.map((part) => (part.type === 'text' ? part.text.trim() : ''))
|
|
402
|
+
.filter(Boolean)
|
|
403
|
+
.join('\n')
|
|
404
|
+
.trim();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function mergeSystemInstructions(...instructions: Array<string | null>) {
|
|
408
|
+
return instructions
|
|
409
|
+
.map((instruction) => instruction?.trim())
|
|
410
|
+
.filter(Boolean)
|
|
411
|
+
.join('\n\n');
|
|
412
|
+
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import type { TypedSupabaseClient } from '@tuturuuu/supabase/next/client';
|
|
2
|
+
import type { PermissionId } from '@tuturuuu/types';
|
|
2
3
|
import { normalizeWorkspaceContextId } from '@tuturuuu/utils/constants';
|
|
3
4
|
import { getPermissions } from '@tuturuuu/utils/workspace-helper';
|
|
4
5
|
import type { NextRequest } from 'next/server';
|
|
5
|
-
import {
|
|
6
|
+
import { createMiraSdkToolSearch } from '../../tools/mira-sdk-tool-search';
|
|
6
7
|
import {
|
|
7
8
|
createMiraStreamTools,
|
|
8
9
|
type MiraToolContext,
|
|
9
10
|
} from '../../tools/mira-tools';
|
|
10
11
|
import type { MiraWorkspaceContextState } from '../../tools/workspace-context';
|
|
11
12
|
import { resolveWorkspaceContextState } from '../../tools/workspace-context';
|
|
13
|
+
import { buildMiraPrompt } from '../mira-prompt';
|
|
12
14
|
import { buildMiraSystemInstruction } from '../mira-system-instruction';
|
|
13
15
|
import type { ChatRequestTaskBoardContext } from './chat-request-schema';
|
|
14
16
|
|
|
15
17
|
type PermissionResultLike = {
|
|
16
|
-
withoutPermission?: (permission:
|
|
18
|
+
withoutPermission?: (permission: PermissionId) => boolean;
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
type SupabaseClientLike = TypedSupabaseClient;
|
|
@@ -171,6 +173,9 @@ export async function prepareMiraRuntime({
|
|
|
171
173
|
}: PrepareMiraRuntimeParams): Promise<{
|
|
172
174
|
miraSystemPrompt?: string;
|
|
173
175
|
miraTools?: ReturnType<typeof createMiraStreamTools>;
|
|
176
|
+
prepareMiraDiscoveryStep?: ReturnType<
|
|
177
|
+
typeof createMiraSdkToolSearch
|
|
178
|
+
>['prepareStep'];
|
|
174
179
|
}> {
|
|
175
180
|
if (!isMiraMode || !wsId) {
|
|
176
181
|
return {};
|
|
@@ -188,26 +193,29 @@ export async function prepareMiraRuntime({
|
|
|
188
193
|
});
|
|
189
194
|
} catch (workspaceContextErr) {
|
|
190
195
|
console.error(
|
|
191
|
-
'Failed to
|
|
196
|
+
'Failed to verify Mira workspace context:',
|
|
192
197
|
workspaceContextErr
|
|
193
198
|
);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
name: 'Current workspace',
|
|
198
|
-
personal: false,
|
|
199
|
-
memberCount: 0,
|
|
199
|
+
return {
|
|
200
|
+
miraSystemPrompt:
|
|
201
|
+
'You are Mira. Workspace access could not be verified for this request. Do not claim to read or change workspace data. Explain that the user can retry after access is restored; answer unrelated general questions normally.',
|
|
200
202
|
};
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
let withoutPermission: PermissionResultLike['withoutPermission'];
|
|
204
206
|
const denyPermissionByDefault = () => true;
|
|
205
207
|
withoutPermission = denyPermissionByDefault;
|
|
208
|
+
const permissionCache = new Map<
|
|
209
|
+
string,
|
|
210
|
+
Promise<PermissionResultLike | null>
|
|
211
|
+
>();
|
|
206
212
|
try {
|
|
207
|
-
const
|
|
213
|
+
const pending = getPermissions({
|
|
208
214
|
wsId: resolvedWorkspaceContext.wsId,
|
|
209
215
|
...(user ? { user } : { request }),
|
|
210
|
-
})
|
|
216
|
+
});
|
|
217
|
+
permissionCache.set(resolvedWorkspaceContext.wsId, pending);
|
|
218
|
+
const permissionsResult = await pending;
|
|
211
219
|
if (permissionsResult?.withoutPermission) {
|
|
212
220
|
withoutPermission = permissionsResult.withoutPermission;
|
|
213
221
|
}
|
|
@@ -223,36 +231,46 @@ export async function prepareMiraRuntime({
|
|
|
223
231
|
chatId,
|
|
224
232
|
supabase: miraSupabase,
|
|
225
233
|
timezone,
|
|
234
|
+
requestHeaders: request.headers,
|
|
235
|
+
authorizeWorkspaceTools: async (targetWsId, permissions) => {
|
|
236
|
+
let pending = permissionCache.get(targetWsId);
|
|
237
|
+
if (!pending) {
|
|
238
|
+
pending = getPermissions({
|
|
239
|
+
wsId: targetWsId,
|
|
240
|
+
...(user ? { user } : { request }),
|
|
241
|
+
});
|
|
242
|
+
permissionCache.set(targetWsId, pending);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const resolved = await pending;
|
|
246
|
+
return (
|
|
247
|
+
!!resolved?.withoutPermission &&
|
|
248
|
+
permissions.every(
|
|
249
|
+
(permission) => !resolved.withoutPermission?.(permission)
|
|
250
|
+
)
|
|
251
|
+
);
|
|
252
|
+
} catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
},
|
|
226
256
|
};
|
|
227
257
|
|
|
228
258
|
let miraSystemPrompt: string;
|
|
229
259
|
try {
|
|
230
|
-
const
|
|
260
|
+
const taskBoardInstruction = await buildTaskBoardContextInstruction({
|
|
261
|
+
resolvedWorkspaceContext,
|
|
262
|
+
supabase: miraSupabase,
|
|
263
|
+
taskBoardContext,
|
|
264
|
+
});
|
|
265
|
+
miraSystemPrompt = await buildMiraPrompt({
|
|
231
266
|
userId,
|
|
232
267
|
wsId: resolvedWorkspaceContext.wsId,
|
|
233
268
|
supabase: miraSupabase,
|
|
234
269
|
timezone,
|
|
235
270
|
withoutPermission,
|
|
271
|
+
workspaceContext: resolvedWorkspaceContext,
|
|
272
|
+
taskBoardInstruction,
|
|
236
273
|
});
|
|
237
|
-
const dynamicInstruction = buildMiraSystemInstruction({
|
|
238
|
-
soul,
|
|
239
|
-
isFirstInteraction,
|
|
240
|
-
withoutPermission,
|
|
241
|
-
});
|
|
242
|
-
const workspaceContextInstruction = `## Workspace Context\n\nCurrent task/calendar/finance workspace context: ${resolvedWorkspaceContext.name} (${resolvedWorkspaceContext.personal ? 'personal' : 'shared'} workspace).\nUse this workspace for "my tasks", "my calendar", and "my finance" requests. Only switch to another workspace when the user explicitly names a different workspace.`;
|
|
243
|
-
const taskBoardContextInstruction = await buildTaskBoardContextInstruction({
|
|
244
|
-
resolvedWorkspaceContext,
|
|
245
|
-
supabase: miraSupabase,
|
|
246
|
-
taskBoardContext,
|
|
247
|
-
});
|
|
248
|
-
miraSystemPrompt = [
|
|
249
|
-
contextString,
|
|
250
|
-
workspaceContextInstruction,
|
|
251
|
-
taskBoardContextInstruction,
|
|
252
|
-
dynamicInstruction,
|
|
253
|
-
]
|
|
254
|
-
.filter(Boolean)
|
|
255
|
-
.join('\n\n');
|
|
256
274
|
} catch (ctxErr) {
|
|
257
275
|
console.error(
|
|
258
276
|
'Failed to build Mira context (continuing with default instruction):',
|
|
@@ -261,8 +279,14 @@ export async function prepareMiraRuntime({
|
|
|
261
279
|
miraSystemPrompt = buildMiraSystemInstruction({ withoutPermission });
|
|
262
280
|
}
|
|
263
281
|
|
|
282
|
+
const discovery = createMiraSdkToolSearch(
|
|
283
|
+
createMiraStreamTools(ctx, withoutPermission, getSteps),
|
|
284
|
+
ctx,
|
|
285
|
+
withoutPermission
|
|
286
|
+
);
|
|
264
287
|
return {
|
|
265
288
|
miraSystemPrompt,
|
|
266
|
-
miraTools:
|
|
289
|
+
miraTools: discovery.tools,
|
|
290
|
+
prepareMiraDiscoveryStep: discovery.prepareStep,
|
|
267
291
|
};
|
|
268
292
|
}
|