experimental-a2 0.12.0 → 0.13.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/CHANGELOG.md +13 -0
- package/dist/ai-server.d.ts +11 -3
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +476 -142
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +32 -1
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +39 -7
- package/dist/ai.js.map +1 -1
- package/docs/guides/06-ai-agents.mdx +100 -18
- package/docs/reference/01-api.mdx +41 -4
- package/examples/playground/app/agent/[agentId]/agent-client.tsx +31 -16
- package/examples/playground/app/agent/[agentId]/compaction/route.ts +14 -0
- package/examples/playground/app/agent/[agentId]/compaction-event.tsx +38 -0
- package/examples/playground/app/agent/[agentId]/compaction-panel.tsx +294 -0
- package/examples/playground/app/agent/compaction-settings.test.ts +128 -0
- package/examples/playground/app/agent/compaction-settings.ts +49 -0
- package/examples/playground/app/agent/compaction-timeline.test.ts +337 -0
- package/examples/playground/app/agent/compaction-timeline.ts +198 -0
- package/examples/playground/app/agent/model.ts +47 -1
- package/examples/playground/app/agent/server.ts +3 -2
- package/examples/playground/app/globals.css +154 -0
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/ai-model-metadata.ts +108 -0
- package/src/ai-sdk-step.ts +5 -1
- package/src/ai-server.ts +712 -203
- package/src/ai.ts +99 -4
|
@@ -1,3 +1,49 @@
|
|
|
1
1
|
import { agent } from 'experimental-a2/ai'
|
|
2
|
+
import type { ContractEvent } from 'experimental-a2'
|
|
3
|
+
import type { AutomaticCompaction } from 'experimental-a2/ai/server'
|
|
4
|
+
import { z } from 'zod'
|
|
2
5
|
|
|
3
|
-
export const
|
|
6
|
+
export const agentModel = 'openai/gpt-5.6-luna'
|
|
7
|
+
|
|
8
|
+
export const compactionSettingsSchema = z.discriminatedUnion('mode', [
|
|
9
|
+
z.strictObject({ mode: z.literal('automatic') }),
|
|
10
|
+
z.strictObject({
|
|
11
|
+
mode: z.literal('manual'),
|
|
12
|
+
thresholdTokens: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
|
|
13
|
+
}),
|
|
14
|
+
z.strictObject({ mode: z.literal('disabled') }),
|
|
15
|
+
])
|
|
16
|
+
|
|
17
|
+
export type CompactionSettings = z.infer<typeof compactionSettingsSchema>
|
|
18
|
+
|
|
19
|
+
export const demoAgent = agent({
|
|
20
|
+
name: 'agent',
|
|
21
|
+
events: { 'compaction.settings.updated': compactionSettingsSchema },
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
export type AgentEvent = ContractEvent<typeof demoAgent.contract.events>
|
|
25
|
+
|
|
26
|
+
const automatic: CompactionSettings = { mode: 'automatic' }
|
|
27
|
+
|
|
28
|
+
export function latestCompactionSettings(history: readonly AgentEvent[]): {
|
|
29
|
+
settings: CompactionSettings
|
|
30
|
+
id: string
|
|
31
|
+
} {
|
|
32
|
+
const event = history.findLast(
|
|
33
|
+
(candidate) => candidate.type === 'compaction.settings.updated',
|
|
34
|
+
)
|
|
35
|
+
return event
|
|
36
|
+
? { settings: event.payload, id: event.id }
|
|
37
|
+
: { settings: automatic, id: 'default' }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function compactionOptions(
|
|
41
|
+
history: readonly AgentEvent[],
|
|
42
|
+
): AutomaticCompaction | false {
|
|
43
|
+
const { settings } = latestCompactionSettings(history)
|
|
44
|
+
return settings.mode === 'disabled'
|
|
45
|
+
? false
|
|
46
|
+
: settings.mode === 'manual'
|
|
47
|
+
? { thresholdTokens: settings.thresholdTokens }
|
|
48
|
+
: {}
|
|
49
|
+
}
|
|
@@ -8,7 +8,7 @@ import { createAgentServer } from 'experimental-a2/ai/server'
|
|
|
8
8
|
import { vercelQueues } from 'experimental-a2/scheduler-vercel'
|
|
9
9
|
import { z } from 'zod'
|
|
10
10
|
import { store } from '@/lib/store'
|
|
11
|
-
import { demoAgent } from './model'
|
|
11
|
+
import { agentModel, compactionOptions, demoAgent } from './model'
|
|
12
12
|
|
|
13
13
|
const maxReminderDelaySeconds = 6 * 24 * 60 * 60
|
|
14
14
|
const bashTimeoutMs = 120_000
|
|
@@ -131,7 +131,8 @@ export const agentServer = createAgentServer({
|
|
|
131
131
|
...(store ? { store } : {}),
|
|
132
132
|
...(agentScheduler ? { scheduler: agentScheduler } : {}),
|
|
133
133
|
agent: demoAgent,
|
|
134
|
-
model:
|
|
134
|
+
model: agentModel,
|
|
135
|
+
compaction: ({ history }) => compactionOptions(history),
|
|
135
136
|
tools,
|
|
136
137
|
instructions: `You are an incident-response agent for the playground.
|
|
137
138
|
When responding to an incident, inspect checkout-api before taking action. Its
|
|
@@ -1489,6 +1489,160 @@ button.danger {
|
|
|
1489
1489
|
|
|
1490
1490
|
/* ── durable agent ──────────────────────────────────────────────── */
|
|
1491
1491
|
|
|
1492
|
+
.agent-compaction {
|
|
1493
|
+
margin: 1rem 0;
|
|
1494
|
+
padding: 0.75rem 1rem;
|
|
1495
|
+
border: 1px solid var(--line);
|
|
1496
|
+
border-radius: 12px;
|
|
1497
|
+
background: var(--surface);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.compaction-marker {
|
|
1501
|
+
display: flex;
|
|
1502
|
+
align-items: center;
|
|
1503
|
+
justify-content: space-between;
|
|
1504
|
+
flex-wrap: wrap;
|
|
1505
|
+
gap: 0.5rem;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.agent-compaction > summary {
|
|
1509
|
+
display: flex;
|
|
1510
|
+
align-items: center;
|
|
1511
|
+
gap: 0.5rem;
|
|
1512
|
+
cursor: pointer;
|
|
1513
|
+
list-style: none;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
.agent-compaction > summary::-webkit-details-marker {
|
|
1517
|
+
display: none;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
.compaction-chevron {
|
|
1521
|
+
color: var(--muted);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
.agent-compaction[open] .compaction-chevron {
|
|
1525
|
+
transform: rotate(90deg);
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
.agent-compaction > summary > strong {
|
|
1529
|
+
flex: 1;
|
|
1530
|
+
font-size: 0.9rem;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
.compaction-controls {
|
|
1534
|
+
margin-top: 0.75rem;
|
|
1535
|
+
padding-top: 0.75rem;
|
|
1536
|
+
border-top: 1px solid var(--line);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
.agent-compaction code {
|
|
1540
|
+
overflow-wrap: anywhere;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
.compaction-metrics {
|
|
1544
|
+
display: grid;
|
|
1545
|
+
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
|
1546
|
+
gap: 0.8rem;
|
|
1547
|
+
margin: 1rem 0;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
.compaction-metrics dt {
|
|
1551
|
+
color: var(--muted);
|
|
1552
|
+
font-size: 0.8rem;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
.compaction-metrics dd {
|
|
1556
|
+
margin: 0.2rem 0 0;
|
|
1557
|
+
font-weight: 600;
|
|
1558
|
+
font-variant-numeric: tabular-nums;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
.compaction-form {
|
|
1562
|
+
margin: 0;
|
|
1563
|
+
padding: 0;
|
|
1564
|
+
border: 0;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
.compaction-form fieldset {
|
|
1568
|
+
margin: 0;
|
|
1569
|
+
padding: 0;
|
|
1570
|
+
border: 0;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
.compaction-fields {
|
|
1574
|
+
display: grid;
|
|
1575
|
+
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
|
|
1576
|
+
gap: 0.75rem;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
.compaction-actions {
|
|
1580
|
+
display: flex;
|
|
1581
|
+
flex-wrap: wrap;
|
|
1582
|
+
gap: 0.5rem;
|
|
1583
|
+
margin-top: 0.75rem;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
.compaction-history {
|
|
1587
|
+
margin-top: 1rem;
|
|
1588
|
+
padding-top: 0.75rem;
|
|
1589
|
+
border-top: 1px solid var(--line);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
.compaction-history summary {
|
|
1593
|
+
cursor: pointer;
|
|
1594
|
+
font-size: 0.9rem;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.compaction-history ol {
|
|
1598
|
+
padding: 0;
|
|
1599
|
+
list-style: none;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
.compaction-history li + li {
|
|
1603
|
+
border-top: 1px solid var(--line);
|
|
1604
|
+
margin-top: 0.75rem;
|
|
1605
|
+
padding-top: 0.75rem;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
.compaction-marker {
|
|
1609
|
+
font-size: 0.85rem;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
.compaction-marker time {
|
|
1613
|
+
color: var(--muted);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
.compaction-history pre,
|
|
1617
|
+
.agent-compaction-event pre {
|
|
1618
|
+
white-space: pre-wrap;
|
|
1619
|
+
overflow-wrap: anywhere;
|
|
1620
|
+
max-height: 20rem;
|
|
1621
|
+
overflow: auto;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
.agent-compaction-event {
|
|
1625
|
+
padding: 0.65rem 0.85rem;
|
|
1626
|
+
border: 1px dashed var(--line);
|
|
1627
|
+
border-radius: 9px;
|
|
1628
|
+
background: var(--code-bg);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
.agent-compaction-event summary {
|
|
1632
|
+
cursor: pointer;
|
|
1633
|
+
font-size: 0.85rem;
|
|
1634
|
+
color: var(--muted);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
.agent-compaction-event summary > span {
|
|
1638
|
+
font-weight: 600;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
.agent-compaction-event time {
|
|
1642
|
+
display: inline-block;
|
|
1643
|
+
margin-left: 0.75rem;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1492
1646
|
.agent-conversation {
|
|
1493
1647
|
display: grid;
|
|
1494
1648
|
overflow: hidden;
|
package/package.json
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { LanguageModel } from 'ai'
|
|
2
|
+
import type { ModelLimits } from './ai.ts'
|
|
3
|
+
|
|
4
|
+
const catalogUrl = 'https://ai-gateway.vercel.sh/v1/models'
|
|
5
|
+
const catalogLifetimeMs = 60 * 60 * 1_000
|
|
6
|
+
const catalogTimeoutMs = 5_000
|
|
7
|
+
|
|
8
|
+
let catalog:
|
|
9
|
+
| {
|
|
10
|
+
expiresAt: number
|
|
11
|
+
promise: Promise<ReadonlyMap<string, ModelLimits>>
|
|
12
|
+
}
|
|
13
|
+
| undefined
|
|
14
|
+
|
|
15
|
+
export const gatewayModelId = (model: LanguageModel): string | undefined => {
|
|
16
|
+
if (typeof model === 'string') {
|
|
17
|
+
return globalThis.AI_SDK_DEFAULT_PROVIDER === undefined ? model : undefined
|
|
18
|
+
}
|
|
19
|
+
return model.provider === 'gateway' ? model.modelId : undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const readCatalog = (): Promise<ReadonlyMap<string, ModelLimits>> => {
|
|
23
|
+
if (catalog && catalog.expiresAt > Date.now()) return catalog.promise
|
|
24
|
+
const entry = {
|
|
25
|
+
expiresAt: Number.POSITIVE_INFINITY,
|
|
26
|
+
promise: (async () => {
|
|
27
|
+
const controller = new AbortController()
|
|
28
|
+
const timeout = setTimeout(
|
|
29
|
+
() => controller.abort(new Error('model catalog request timed out')),
|
|
30
|
+
catalogTimeoutMs,
|
|
31
|
+
)
|
|
32
|
+
timeout.unref?.()
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(catalogUrl, { signal: controller.signal })
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new Error(
|
|
37
|
+
`model catalog request failed: HTTP ${response.status}`,
|
|
38
|
+
)
|
|
39
|
+
const body: unknown = await response.json()
|
|
40
|
+
if (
|
|
41
|
+
typeof body !== 'object' ||
|
|
42
|
+
body === null ||
|
|
43
|
+
!('data' in body) ||
|
|
44
|
+
!Array.isArray(body.data)
|
|
45
|
+
) {
|
|
46
|
+
throw new Error('model catalog response must contain a data array')
|
|
47
|
+
}
|
|
48
|
+
const models = new Map<string, ModelLimits>()
|
|
49
|
+
for (const row of body.data as unknown[]) {
|
|
50
|
+
if (
|
|
51
|
+
typeof row !== 'object' ||
|
|
52
|
+
row === null ||
|
|
53
|
+
!('type' in row) ||
|
|
54
|
+
!('id' in row) ||
|
|
55
|
+
!('context_window' in row) ||
|
|
56
|
+
!('max_tokens' in row) ||
|
|
57
|
+
row.type !== 'language' ||
|
|
58
|
+
typeof row.id !== 'string' ||
|
|
59
|
+
typeof row.context_window !== 'number' ||
|
|
60
|
+
typeof row.max_tokens !== 'number' ||
|
|
61
|
+
!Number.isSafeInteger(row.context_window) ||
|
|
62
|
+
row.context_window <= 0 ||
|
|
63
|
+
!Number.isSafeInteger(row.max_tokens) ||
|
|
64
|
+
row.max_tokens <= 0
|
|
65
|
+
)
|
|
66
|
+
continue
|
|
67
|
+
models.set(row.id, {
|
|
68
|
+
contextWindow: row.context_window,
|
|
69
|
+
maxOutputTokens: row.max_tokens,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
return models
|
|
73
|
+
} finally {
|
|
74
|
+
clearTimeout(timeout)
|
|
75
|
+
}
|
|
76
|
+
})(),
|
|
77
|
+
}
|
|
78
|
+
catalog = entry
|
|
79
|
+
entry.promise = entry.promise.then(
|
|
80
|
+
(models) => {
|
|
81
|
+
entry.expiresAt = Date.now() + catalogLifetimeMs
|
|
82
|
+
return models
|
|
83
|
+
},
|
|
84
|
+
(error: unknown) => {
|
|
85
|
+
if (catalog === entry) catalog = undefined
|
|
86
|
+
throw error
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
return entry.promise
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const readModelLimits = async (options: {
|
|
93
|
+
modelId: string
|
|
94
|
+
signal: AbortSignal
|
|
95
|
+
}): Promise<ModelLimits | null> => {
|
|
96
|
+
options.signal.throwIfAborted()
|
|
97
|
+
let onAbort!: () => void
|
|
98
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
99
|
+
onAbort = () => reject(options.signal.reason)
|
|
100
|
+
options.signal.addEventListener('abort', onAbort, { once: true })
|
|
101
|
+
})
|
|
102
|
+
try {
|
|
103
|
+
const models = await Promise.race([readCatalog(), aborted])
|
|
104
|
+
return models.get(options.modelId) ?? null
|
|
105
|
+
} finally {
|
|
106
|
+
options.signal.removeEventListener('abort', onAbort)
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/ai-sdk-step.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
Instructions,
|
|
11
11
|
LanguageModel,
|
|
12
12
|
LanguageModelUsage,
|
|
13
|
+
ModelMessage,
|
|
13
14
|
TextStreamPart,
|
|
14
15
|
ToolSet,
|
|
15
16
|
UIMessage,
|
|
@@ -127,6 +128,7 @@ export type AISDKStepInput<M extends UIMessage, T extends ToolSet> = {
|
|
|
127
128
|
model: LanguageModel
|
|
128
129
|
tools: T
|
|
129
130
|
messages: M[]
|
|
131
|
+
modelMessages?: ModelMessage[]
|
|
130
132
|
responseMessageId: string
|
|
131
133
|
abortSignal: AbortSignal
|
|
132
134
|
instructions?: Instructions
|
|
@@ -201,7 +203,9 @@ export async function generateAISDKStep<M extends UIMessage, T extends ToolSet>(
|
|
|
201
203
|
): Promise<AISDKStepResult<M>> {
|
|
202
204
|
const settings = safeSettings(input.settings)
|
|
203
205
|
const tools = modelToolSet(input.tools)
|
|
204
|
-
const messages =
|
|
206
|
+
const messages =
|
|
207
|
+
input.modelMessages ??
|
|
208
|
+
(await convertToModelMessages(input.messages, { tools }))
|
|
205
209
|
const streamOptions = {
|
|
206
210
|
...settings,
|
|
207
211
|
model: input.model,
|