@nuxtagentic/prompt 0.0.0-stage → 0.1.1
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/LICENSE.md +9 -0
- package/README.md +91 -2
- package/cloudconfig.ts +165 -0
- package/config.ts +33 -0
- package/core/decisions.ts +155 -0
- package/core/engine.ts +317 -0
- package/core/index.ts +3 -0
- package/core/planner.ts +67 -0
- package/core/types.ts +188 -0
- package/core/validation.ts +187 -0
- package/docs/search.md +69 -0
- package/module.ts +26 -0
- package/package.json +56 -3
- package/runtime/server/api/agent/cancel.post.ts +17 -0
- package/runtime/server/api/agent/execute.post.ts +26 -0
- package/runtime/server/api/agent/plan.post.ts +37 -0
- package/runtime/server/api/agent.post.ts +60 -0
- package/runtime/server/confirmations.ts +104 -0
- package/runtime/server/utils/agent-operations.ts +112 -0
- package/runtime/server/utils/agent-session.ts +14 -0
- package/runtime/server/utils/decision-arguments.ts +363 -0
- package/runtime/server/utils/decision-provider.ts +141 -0
- package/runtime/server/utils/jev-answers.ts +55 -0
- package/runtime/server/utils/jev-dispatch.ts +156 -0
- package/runtime/server/utils/jev-inference.ts +87 -0
- package/runtime/server/utils/jev-tool-plan.ts +64 -0
- package/runtime/server/utils/model-query-condition.ts +59 -0
- package/runtime/server/utils/model-query.ts +275 -0
- package/runtime/server/utils/prompt-candidates.ts +165 -0
- package/runtime/server/utils/prompt-dates.ts +83 -0
- package/runtime/shared/agent-tools.ts +50 -0
- package/runtime/shared/agent.ts +5 -0
- package/runtime/shared/decision-provider.ts +1 -0
- package/runtime/shared/prompt-config.ts +27 -0
- package/schema.ts +69 -0
- package/server-contributions.ts +53 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import type { Scalar } from '@nuxtagentic/data/runtime/shared/resource-query'
|
|
2
|
+
import type { PromptConfig } from '../../shared/prompt-config'
|
|
3
|
+
import { findDateCandidates } from './prompt-dates'
|
|
4
|
+
|
|
5
|
+
export interface ValueCandidate {
|
|
6
|
+
value: Scalar
|
|
7
|
+
label: string
|
|
8
|
+
end?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface NumberCandidate extends ValueCandidate {
|
|
12
|
+
value: number
|
|
13
|
+
unit?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PromptCandidates {
|
|
17
|
+
now: Date
|
|
18
|
+
text: string[]
|
|
19
|
+
spans: string[]
|
|
20
|
+
numbers: NumberCandidate[]
|
|
21
|
+
dates: ValueCandidate[]
|
|
22
|
+
limits: string[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Rank phrase boundaries; never remove these words from a literal value.
|
|
26
|
+
const STOPWORDS = new Set(
|
|
27
|
+
'a an the and or of in on at to for from by with without is are was were be been has have had do does did me my we our us you your i it its all any every some only just please can could would show list find get give see display filter search sort order named called titled where what when how'.split(
|
|
28
|
+
' ',
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
function textCandidates(prompt: string, maxWords: number, limit: number): string[] {
|
|
33
|
+
const quoted = [...prompt.matchAll(/"([^"]+)"|(?<![\p{L}\p{N}])'([^']+)'|“([^”]+)”/gu)].map(
|
|
34
|
+
(match) => (match[1] ?? match[2] ?? match[3])!.trim(),
|
|
35
|
+
)
|
|
36
|
+
const emails = prompt.match(/[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g) ?? []
|
|
37
|
+
const named = [
|
|
38
|
+
...prompt.matchAll(
|
|
39
|
+
/\b(?:named|called|titled)\s+(.+?)(?=\s+(?:on|at|from|for|where|with)\b|[.!?]?$)/gi,
|
|
40
|
+
),
|
|
41
|
+
].map((match) => match[1]!.trim())
|
|
42
|
+
const words = [...prompt.matchAll(/[\p{L}\p{N}@._+-]+(?:['’][\p{L}\p{N}]+)*/gu)]
|
|
43
|
+
const groups = Array.from({ length: Math.min(maxWords, words.length) }, (_, index) => {
|
|
44
|
+
const length = index + 1
|
|
45
|
+
const spans = []
|
|
46
|
+
for (let i = 0; i + length <= words.length; i++) {
|
|
47
|
+
const first = words[i]!
|
|
48
|
+
const last = words[i + length - 1]!
|
|
49
|
+
spans.push({
|
|
50
|
+
text: prompt.slice(first.index!, last.index! + last[0].length),
|
|
51
|
+
rank:
|
|
52
|
+
Number(!STOPWORDS.has(first[0].toLowerCase())) +
|
|
53
|
+
Number(!STOPWORDS.has(last[0].toLowerCase())),
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
return spans.sort((a, b) => b.rank - a.rank)
|
|
57
|
+
})
|
|
58
|
+
const result = new Set([...quoted, ...emails, ...named].filter(Boolean))
|
|
59
|
+
for (let offset = 0; result.size < limit; offset++) {
|
|
60
|
+
let found = false
|
|
61
|
+
for (const group of groups) {
|
|
62
|
+
const span = group[offset]
|
|
63
|
+
if (!span) {
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
found = true
|
|
67
|
+
result.add(span.text)
|
|
68
|
+
if (result.size >= limit) {
|
|
69
|
+
break
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!found) {
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return [...result].slice(0, limit)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function prepareCandidates(
|
|
80
|
+
prompt: string,
|
|
81
|
+
config: PromptConfig,
|
|
82
|
+
now = new Date(),
|
|
83
|
+
): PromptCandidates {
|
|
84
|
+
const text = textCandidates(
|
|
85
|
+
prompt,
|
|
86
|
+
config.candidates.maxWords,
|
|
87
|
+
Math.min(253, Math.max(config.candidates.text, config.candidates.span)),
|
|
88
|
+
)
|
|
89
|
+
const numbers = [...prompt.matchAll(/-?\d+(?:\.\d+)?(?:\s*(?:KB|MB|GB))?/gi)].map((match) => {
|
|
90
|
+
const [, amount, unit] = /(-?\d+(?:\.\d+)?)\s*(KB|MB|GB)?/i.exec(match[0])!
|
|
91
|
+
return { value: Number(amount), label: match[0], unit: unit?.toUpperCase() }
|
|
92
|
+
})
|
|
93
|
+
return {
|
|
94
|
+
now,
|
|
95
|
+
text: text.slice(0, config.candidates.text),
|
|
96
|
+
spans: text.slice(0, config.candidates.span),
|
|
97
|
+
numbers,
|
|
98
|
+
dates: findDateCandidates(prompt, now),
|
|
99
|
+
limits: [...new Set(prompt.match(/\b\d+\b/g) ?? [])].slice(0, 253),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function findFieldCandidates(
|
|
104
|
+
context: PromptCandidates,
|
|
105
|
+
field: { kind: string; options?: readonly string[] },
|
|
106
|
+
vocabulary: Record<string, string> = {},
|
|
107
|
+
unit?: 'bytes',
|
|
108
|
+
): ValueCandidate[] {
|
|
109
|
+
let values: ValueCandidate[]
|
|
110
|
+
switch (field.kind) {
|
|
111
|
+
case 'boolean':
|
|
112
|
+
values = [
|
|
113
|
+
{ value: true, label: 'true / yes / on / 1' },
|
|
114
|
+
{ value: false, label: 'false / no / off / 0' },
|
|
115
|
+
]
|
|
116
|
+
break
|
|
117
|
+
case 'select':
|
|
118
|
+
values = (field.options ?? []).map((value) => ({
|
|
119
|
+
value,
|
|
120
|
+
label: vocabulary[value] ? `${value}: ${vocabulary[value]}` : value,
|
|
121
|
+
}))
|
|
122
|
+
break
|
|
123
|
+
case 'number':
|
|
124
|
+
case 'float':
|
|
125
|
+
case 'relation':
|
|
126
|
+
values = context.numbers.map((candidate) => ({
|
|
127
|
+
value:
|
|
128
|
+
candidate.value *
|
|
129
|
+
(unit === 'bytes' && candidate.unit
|
|
130
|
+
? { KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 }[candidate.unit as 'KB' | 'MB' | 'GB']
|
|
131
|
+
: 1),
|
|
132
|
+
label: candidate.label,
|
|
133
|
+
}))
|
|
134
|
+
break
|
|
135
|
+
case 'date':
|
|
136
|
+
case 'datetime':
|
|
137
|
+
values = context.dates.map((candidate) =>
|
|
138
|
+
field.kind === 'date'
|
|
139
|
+
? {
|
|
140
|
+
...candidate,
|
|
141
|
+
value: String(candidate.value).slice(0, 10),
|
|
142
|
+
end: candidate.end?.slice(0, 10),
|
|
143
|
+
}
|
|
144
|
+
: candidate,
|
|
145
|
+
)
|
|
146
|
+
break
|
|
147
|
+
default:
|
|
148
|
+
values = [
|
|
149
|
+
...Object.entries(vocabulary).map(([value, meaning]) => ({
|
|
150
|
+
value,
|
|
151
|
+
label: `${value}: ${meaning}`,
|
|
152
|
+
})),
|
|
153
|
+
...context.text.map((value) => ({ value, label: value })),
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
const unique = new Map<string, ValueCandidate>()
|
|
157
|
+
for (const candidate of values) {
|
|
158
|
+
const key = JSON.stringify([candidate.value, candidate.end])
|
|
159
|
+
if (!unique.has(key)) {
|
|
160
|
+
unique.set(key, candidate)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Reserve three of Choice's 255 options: actual null, absent and unavailable.
|
|
164
|
+
return [...[...unique.values()].slice(0, 252), { value: null, label: 'missing / null' }]
|
|
165
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { findNamedMonthPeriods } from '@nuxtagentic/data/runtime/shared/date-periods'
|
|
2
|
+
import type { ValueCandidate } from './prompt-candidates'
|
|
3
|
+
|
|
4
|
+
const millisecondsPerDay = 86_400_000
|
|
5
|
+
const millisecondsPerUnit: Record<string, number> = {
|
|
6
|
+
hour: 3_600_000,
|
|
7
|
+
day: millisecondsPerDay,
|
|
8
|
+
week: 7 * millisecondsPerDay,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function findDateCandidates(prompt: string, now: Date): ValueCandidate[] {
|
|
12
|
+
const values: ValueCandidate[] = []
|
|
13
|
+
const day = (year: number, month: number, date: number) =>
|
|
14
|
+
new Date(Date.UTC(year, month, date)).toISOString()
|
|
15
|
+
const year = now.getUTCFullYear()
|
|
16
|
+
const month = now.getUTCMonth()
|
|
17
|
+
const date = now.getUTCDate()
|
|
18
|
+
const weekday = (now.getUTCDay() + 6) % 7
|
|
19
|
+
const periods: Record<string, [string, string]> = {
|
|
20
|
+
today: [day(year, month, date), day(year, month, date + 1)],
|
|
21
|
+
yesterday: [day(year, month, date - 1), day(year, month, date)],
|
|
22
|
+
tomorrow: [day(year, month, date + 1), day(year, month, date + 2)],
|
|
23
|
+
'this week': [day(year, month, date - weekday), day(year, month, date - weekday + 7)],
|
|
24
|
+
'last week': [day(year, month, date - weekday - 7), day(year, month, date - weekday)],
|
|
25
|
+
'next week': [day(year, month, date - weekday + 7), day(year, month, date - weekday + 14)],
|
|
26
|
+
'this month': [day(year, month, 1), day(year, month + 1, 1)],
|
|
27
|
+
'last month': [day(year, month - 1, 1), day(year, month, 1)],
|
|
28
|
+
'next month': [day(year, month + 1, 1), day(year, month + 2, 1)],
|
|
29
|
+
'this year': [day(year, 0, 1), day(year + 1, 0, 1)],
|
|
30
|
+
'last year': [day(year - 1, 0, 1), day(year, 0, 1)],
|
|
31
|
+
'next year': [day(year + 1, 0, 1), day(year + 2, 0, 1)],
|
|
32
|
+
}
|
|
33
|
+
for (const [label, [value, end]] of Object.entries(periods)) {
|
|
34
|
+
if (new RegExp(`\\b${label}\\b`, 'i').test(prompt)) {
|
|
35
|
+
values.push({ label: `${label} (${value} until ${end}, exclusive)`, value, end })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const { label, start, end } of findNamedMonthPeriods(prompt, now)) {
|
|
39
|
+
values.push({ label: `${label} (${start} until ${end}, exclusive)`, value: start, end })
|
|
40
|
+
}
|
|
41
|
+
for (const match of prompt.matchAll(
|
|
42
|
+
/\b(?:last|past|previous)\s+(\d+)\s+(hours?|days?|weeks?|months?|years?)\b/gi,
|
|
43
|
+
)) {
|
|
44
|
+
const amount = Number(match[1])
|
|
45
|
+
const unit = match[2]!.toLowerCase().replace(/s$/, '')
|
|
46
|
+
const start = new Date(now)
|
|
47
|
+
if (unit === 'year' || unit === 'month') {
|
|
48
|
+
const targetYear = unit === 'year' ? year - amount : year
|
|
49
|
+
const targetMonth = unit === 'month' ? month - amount : month
|
|
50
|
+
const lastDay = new Date(Date.UTC(targetYear, targetMonth + 1, 0)).getUTCDate()
|
|
51
|
+
start.setUTCDate(Math.min(date, lastDay))
|
|
52
|
+
start.setUTCFullYear(targetYear, targetMonth)
|
|
53
|
+
} else {
|
|
54
|
+
const duration = millisecondsPerUnit[unit] ?? millisecondsPerDay
|
|
55
|
+
start.setTime(now.getTime() - amount * duration)
|
|
56
|
+
}
|
|
57
|
+
if (Number.isFinite(start.getTime())) {
|
|
58
|
+
values.push({ label: match[0], value: start.toISOString(), end: now.toISOString() })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const match of prompt.matchAll(
|
|
62
|
+
/\b\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})?)?\b/g,
|
|
63
|
+
)) {
|
|
64
|
+
const parsed = new Date(match[0])
|
|
65
|
+
if (Number.isFinite(parsed.getTime())) {
|
|
66
|
+
values.push({
|
|
67
|
+
label: match[0],
|
|
68
|
+
value: parsed.toISOString(),
|
|
69
|
+
...(match[0].length === 10
|
|
70
|
+
? { end: new Date(parsed.getTime() + 86400000).toISOString() }
|
|
71
|
+
: {}),
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const match of prompt.matchAll(/\b(?:19|20)\d{2}\b/g)) {
|
|
76
|
+
if (prompt.slice(match.index! + 4, match.index! + 5) === '-') {
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
const y = Number(match[0])
|
|
80
|
+
values.push({ label: `year ${y}`, value: day(y, 0, 1), end: day(y + 1, 0, 1) })
|
|
81
|
+
}
|
|
82
|
+
return values
|
|
83
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Closed choices, boolean flags, or literal spans copied from the prompt. */
|
|
2
|
+
export type ArgSpec =
|
|
3
|
+
| {
|
|
4
|
+
kind: 'choice'
|
|
5
|
+
question: string
|
|
6
|
+
stated?: string
|
|
7
|
+
options: Record<string, string>
|
|
8
|
+
}
|
|
9
|
+
| { kind: 'flag'; question: string }
|
|
10
|
+
| { kind: 'span'; question: string; stated: string }
|
|
11
|
+
|
|
12
|
+
export interface ToolSpec {
|
|
13
|
+
/** What the tool does, in the words a user might use. Jev routes on this. */
|
|
14
|
+
description: string
|
|
15
|
+
args: Record<string, ArgSpec>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type AgentToolName = string
|
|
19
|
+
|
|
20
|
+
export type ArgValue = string | string[] | boolean
|
|
21
|
+
|
|
22
|
+
export interface ToolCallArgument {
|
|
23
|
+
/** Left out because the prompt did not mention it. */
|
|
24
|
+
omitted: boolean
|
|
25
|
+
value?: ArgValue
|
|
26
|
+
/** Normalized decision confidence, from 0 to 1. */
|
|
27
|
+
confidence: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What the server answers for a prompt. `tool: null` means no tool fits. */
|
|
31
|
+
export interface AgentToolCall {
|
|
32
|
+
tool: AgentToolName | null
|
|
33
|
+
args: Record<string, ArgValue>
|
|
34
|
+
arguments: Record<string, ToolCallArgument>
|
|
35
|
+
/** The least certain judgement behind the call. */
|
|
36
|
+
confidence: number
|
|
37
|
+
weakest: string | null
|
|
38
|
+
model: string
|
|
39
|
+
/** Validated, read-only model query assembled from schema-derived Jev decisions. */
|
|
40
|
+
resource?: {
|
|
41
|
+
id: string
|
|
42
|
+
query: import('@nuxtagentic/data/runtime/shared/resource-query').ResourceQuery
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const JEV_MODELS = [
|
|
47
|
+
{ id: 'jev-latest', name: 'Jev latest', description: 'Stable release' },
|
|
48
|
+
{ id: 'jev-preview', name: 'Jev preview', description: 'Newest build' },
|
|
49
|
+
{ id: 'jev-1.13.0', name: 'Jev 1.13', description: 'Pinned version' },
|
|
50
|
+
] as const
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type DecisionProvider = 'openAI' | 'typesafeAI'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface InferencePolicy {
|
|
2
|
+
/** Total time across all stages and SDK retries. */
|
|
3
|
+
deadlineMs: number
|
|
4
|
+
/** Timeout of an individual SDK attempt. */
|
|
5
|
+
timeoutMs: number
|
|
6
|
+
maxRetries: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PromptConfig {
|
|
10
|
+
model: string
|
|
11
|
+
instructions: string
|
|
12
|
+
maxConstraintsPerField: number
|
|
13
|
+
candidates: { span: number; text: number; maxWords: number }
|
|
14
|
+
/** Bound speculative model extraction on global requests. */
|
|
15
|
+
routing: { maxInlineModels: number; maxInlineQuestions: number; maxInlineBytes: number }
|
|
16
|
+
inference: InferencePolicy
|
|
17
|
+
rerank: InferencePolicy & {
|
|
18
|
+
maxFiles: number
|
|
19
|
+
excerptChars: number
|
|
20
|
+
queryChars: number
|
|
21
|
+
nameChars: number
|
|
22
|
+
folderChars: number
|
|
23
|
+
mimeTypeChars: number
|
|
24
|
+
}
|
|
25
|
+
/** Log timing and usage, without prompts, candidates or answers. */
|
|
26
|
+
logMetrics: boolean
|
|
27
|
+
}
|
package/schema.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { model, t } from '@cloudconfig/core'
|
|
2
|
+
|
|
3
|
+
export const agentOperationsModels = {
|
|
4
|
+
// Durable, session-bound reviews. Public CRUD cannot write any of these private fields.
|
|
5
|
+
agentOperation: model(
|
|
6
|
+
{
|
|
7
|
+
key: t.text().required().unique().serverOnly(),
|
|
8
|
+
ownerId: t.number().required().serverOnly(),
|
|
9
|
+
sessionId: t.text().required().serverOnly(),
|
|
10
|
+
expiresAt: t.datetime().required().serverOnly(),
|
|
11
|
+
state: t
|
|
12
|
+
.select(['pending', 'executing', 'completed', 'failed', 'cancelled'])
|
|
13
|
+
.default('pending')
|
|
14
|
+
.serverOnly(),
|
|
15
|
+
plan: t.json().required().serverOnly(),
|
|
16
|
+
result: t.json().serverOnly(),
|
|
17
|
+
version: t.number().default(0).serverOnly(),
|
|
18
|
+
expectedVersion: t.number().default(-1).serverOnly(),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
access: {
|
|
22
|
+
list: ({ user, where }) => (user ? where('ownerId', 'eq', user.id) : false),
|
|
23
|
+
read: ({ user, where }) => (user ? where('ownerId', 'eq', user.id) : false),
|
|
24
|
+
create: ({ user, input }) =>
|
|
25
|
+
!!user && input?.ownerId === user.id && !!input?.plan && input?.state === 'pending',
|
|
26
|
+
update: ({ user, input, where }) =>
|
|
27
|
+
user && Number.isSafeInteger(input?.expectedVersion)
|
|
28
|
+
? where('ownerId', 'eq', user.id)
|
|
29
|
+
: false,
|
|
30
|
+
},
|
|
31
|
+
validate: (input, existing) => {
|
|
32
|
+
const issues = []
|
|
33
|
+
if (
|
|
34
|
+
!/^[0-9a-f-]{36}$/.test(String(input.key)) ||
|
|
35
|
+
!String(input.sessionId) ||
|
|
36
|
+
!Number.isFinite(Date.parse(String(input.expiresAt))) ||
|
|
37
|
+
JSON.stringify(input.plan).length > 100_000
|
|
38
|
+
) {
|
|
39
|
+
issues.push({ path: 'plan', message: 'Invalid action confirmation.' })
|
|
40
|
+
}
|
|
41
|
+
if (
|
|
42
|
+
existing &&
|
|
43
|
+
(input.expectedVersion !== existing.version ||
|
|
44
|
+
input.version !== Number(existing.version) + 1)
|
|
45
|
+
) {
|
|
46
|
+
issues.push({ path: 'version', message: 'This action was already claimed or changed.' })
|
|
47
|
+
}
|
|
48
|
+
if (existing) {
|
|
49
|
+
const transitions: Record<string, string[]> = {
|
|
50
|
+
pending: ['executing', 'cancelled'],
|
|
51
|
+
executing: ['completed', 'failed'],
|
|
52
|
+
}
|
|
53
|
+
if (
|
|
54
|
+
!(transitions[String(existing.state)] ?? []).includes(String(input.state)) ||
|
|
55
|
+
['key', 'ownerId', 'sessionId', 'expiresAt'].some(
|
|
56
|
+
(key) => input[key] !== existing[key],
|
|
57
|
+
) ||
|
|
58
|
+
JSON.stringify(input.plan) !== JSON.stringify(existing.plan)
|
|
59
|
+
) {
|
|
60
|
+
issues.push({ path: 'state', message: 'Invalid action confirmation transition.' })
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return issues
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type AgentOperationsConfig = { models: typeof agentOperationsModels }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ClientOptions } from '@cloudconfig/core/client'
|
|
2
|
+
import type { H3Event } from 'h3'
|
|
3
|
+
import type { ModelView } from '@nuxtagentic/data/runtime/shared/model-views'
|
|
4
|
+
import type { ToolSpec } from './runtime/shared/agent-tools'
|
|
5
|
+
import type { Resource } from '@nuxtagentic/prompt/core/index'
|
|
6
|
+
|
|
7
|
+
export interface WorkspaceMetadata {
|
|
8
|
+
views?: readonly ModelView[]
|
|
9
|
+
tools?: Record<string, ToolSpec>
|
|
10
|
+
resourceAliases?: Record<string, readonly string[]>
|
|
11
|
+
excludedModels?: readonly string[]
|
|
12
|
+
googleServices?: Record<
|
|
13
|
+
string,
|
|
14
|
+
{
|
|
15
|
+
label: string
|
|
16
|
+
scopes: readonly string[]
|
|
17
|
+
disconnect?: {
|
|
18
|
+
dataPrefixes?: readonly string[]
|
|
19
|
+
userStatePrefixes?: readonly string[]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AgentSession {
|
|
26
|
+
user: { id: string | number; [key: string]: unknown }
|
|
27
|
+
session: { id: string | number; activeOrganizationId?: string | number | null }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AgentResourceFactory = (context: {
|
|
31
|
+
event: H3Event
|
|
32
|
+
authBase: string
|
|
33
|
+
session: AgentSession
|
|
34
|
+
clientOptions: ClientOptions
|
|
35
|
+
}) => Resource | Promise<Resource>
|
|
36
|
+
|
|
37
|
+
export type GoogleDisconnectHook = (input: {
|
|
38
|
+
event: H3Event
|
|
39
|
+
service: string
|
|
40
|
+
user: { id: number }
|
|
41
|
+
}) => void | Promise<void>
|
|
42
|
+
|
|
43
|
+
export type GoogleAvailabilityHook = (input: {
|
|
44
|
+
event: H3Event
|
|
45
|
+
service: string
|
|
46
|
+
user: { id: number; email: string }
|
|
47
|
+
}) => string | undefined | Promise<string | undefined>
|
|
48
|
+
|
|
49
|
+
export interface ServerContribution extends WorkspaceMetadata {
|
|
50
|
+
resourceFactories?: readonly AgentResourceFactory[]
|
|
51
|
+
googleDisconnectHooks?: readonly GoogleDisconnectHook[]
|
|
52
|
+
googleAvailabilityHooks?: readonly GoogleAvailabilityHook[]
|
|
53
|
+
}
|