@yolk-sdk/emulators 0.1.0-canary.96
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 +21 -0
- package/README.md +2135 -0
- package/dist/anthropic.d.mts +269 -0
- package/dist/anthropic.d.mts.map +1 -0
- package/dist/anthropic.mjs +177 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/chat-completions.d.mts +288 -0
- package/dist/chat-completions.d.mts.map +1 -0
- package/dist/chat-completions.mjs +451 -0
- package/dist/chat-completions.mjs.map +1 -0
- package/dist/codex.d.mts +265 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +199 -0
- package/dist/codex.mjs.map +1 -0
- package/dist/dropbox/api.d.mts +63 -0
- package/dist/dropbox/api.d.mts.map +1 -0
- package/dist/dropbox/api.mjs +565 -0
- package/dist/dropbox/api.mjs.map +1 -0
- package/dist/dropbox/state.d.mts +136 -0
- package/dist/dropbox/state.d.mts.map +1 -0
- package/dist/dropbox/state.mjs +209 -0
- package/dist/dropbox/state.mjs.map +1 -0
- package/dist/dropbox.d.mts +79 -0
- package/dist/dropbox.d.mts.map +1 -0
- package/dist/dropbox.mjs +124 -0
- package/dist/dropbox.mjs.map +1 -0
- package/dist/email-fixtures.d.mts +36 -0
- package/dist/email-fixtures.d.mts.map +1 -0
- package/dist/email-fixtures.mjs +1080 -0
- package/dist/email-fixtures.mjs.map +1 -0
- package/dist/email.d.mts +160 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +608 -0
- package/dist/email.mjs.map +1 -0
- package/dist/emulator-compose.d.mts +40 -0
- package/dist/emulator-compose.d.mts.map +1 -0
- package/dist/emulator-compose.mjs +91 -0
- package/dist/emulator-compose.mjs.map +1 -0
- package/dist/emulator-http.d.mts +53 -0
- package/dist/emulator-http.d.mts.map +1 -0
- package/dist/emulator-http.mjs +117 -0
- package/dist/emulator-http.mjs.map +1 -0
- package/dist/emulator-kernel.d.mts +176 -0
- package/dist/emulator-kernel.d.mts.map +1 -0
- package/dist/emulator-kernel.mjs +413 -0
- package/dist/emulator-kernel.mjs.map +1 -0
- package/dist/fixture-route.d.mts +129 -0
- package/dist/fixture-route.d.mts.map +1 -0
- package/dist/fixture-route.mjs +257 -0
- package/dist/fixture-route.mjs.map +1 -0
- package/dist/fortnox/api.d.mts +92 -0
- package/dist/fortnox/api.d.mts.map +1 -0
- package/dist/fortnox/api.mjs +750 -0
- package/dist/fortnox/api.mjs.map +1 -0
- package/dist/fortnox/state.d.mts +533 -0
- package/dist/fortnox/state.d.mts.map +1 -0
- package/dist/fortnox/state.mjs +612 -0
- package/dist/fortnox/state.mjs.map +1 -0
- package/dist/fortnox.d.mts +128 -0
- package/dist/fortnox.d.mts.map +1 -0
- package/dist/fortnox.mjs +403 -0
- package/dist/fortnox.mjs.map +1 -0
- package/dist/gateway-evaluate-recordings.d.mts +12 -0
- package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
- package/dist/gateway-evaluate-recordings.mjs +128 -0
- package/dist/gateway-evaluate-recordings.mjs.map +1 -0
- package/dist/gateway.d.mts +272 -0
- package/dist/gateway.d.mts.map +1 -0
- package/dist/gateway.mjs +400 -0
- package/dist/gateway.mjs.map +1 -0
- package/dist/github/api.d.mts +63 -0
- package/dist/github/api.d.mts.map +1 -0
- package/dist/github/api.mjs +527 -0
- package/dist/github/api.mjs.map +1 -0
- package/dist/github/state.d.mts +272 -0
- package/dist/github/state.d.mts.map +1 -0
- package/dist/github/state.mjs +303 -0
- package/dist/github/state.mjs.map +1 -0
- package/dist/github.d.mts +77 -0
- package/dist/github.d.mts.map +1 -0
- package/dist/github.mjs +180 -0
- package/dist/github.mjs.map +1 -0
- package/dist/google/calendar.d.mts +10 -0
- package/dist/google/calendar.d.mts.map +1 -0
- package/dist/google/calendar.mjs +252 -0
- package/dist/google/calendar.mjs.map +1 -0
- package/dist/google/drive.d.mts +8 -0
- package/dist/google/drive.d.mts.map +1 -0
- package/dist/google/drive.mjs +252 -0
- package/dist/google/drive.mjs.map +1 -0
- package/dist/google/gmail.d.mts +10 -0
- package/dist/google/gmail.d.mts.map +1 -0
- package/dist/google/gmail.mjs +591 -0
- package/dist/google/gmail.mjs.map +1 -0
- package/dist/google/shared.d.mts +107 -0
- package/dist/google/shared.d.mts.map +1 -0
- package/dist/google/shared.mjs +146 -0
- package/dist/google/shared.mjs.map +1 -0
- package/dist/google/state.d.mts +385 -0
- package/dist/google/state.d.mts.map +1 -0
- package/dist/google/state.mjs +496 -0
- package/dist/google/state.mjs.map +1 -0
- package/dist/google.d.mts +75 -0
- package/dist/google.d.mts.map +1 -0
- package/dist/google.mjs +210 -0
- package/dist/google.mjs.map +1 -0
- package/dist/linkedin-search/api.d.mts +45 -0
- package/dist/linkedin-search/api.d.mts.map +1 -0
- package/dist/linkedin-search/api.mjs +181 -0
- package/dist/linkedin-search/api.mjs.map +1 -0
- package/dist/linkedin-search/state.d.mts +123 -0
- package/dist/linkedin-search/state.d.mts.map +1 -0
- package/dist/linkedin-search/state.mjs +261 -0
- package/dist/linkedin-search/state.mjs.map +1 -0
- package/dist/linkedin-search.d.mts +69 -0
- package/dist/linkedin-search.d.mts.map +1 -0
- package/dist/linkedin-search.mjs +158 -0
- package/dist/linkedin-search.mjs.map +1 -0
- package/dist/mcp/api.d.mts +44 -0
- package/dist/mcp/api.d.mts.map +1 -0
- package/dist/mcp/api.mjs +557 -0
- package/dist/mcp/api.mjs.map +1 -0
- package/dist/mcp/recordings.d.mts +40 -0
- package/dist/mcp/recordings.d.mts.map +1 -0
- package/dist/mcp/recordings.mjs +2390 -0
- package/dist/mcp/recordings.mjs.map +1 -0
- package/dist/mcp/state.d.mts +71 -0
- package/dist/mcp/state.d.mts.map +1 -0
- package/dist/mcp/state.mjs +83 -0
- package/dist/mcp/state.mjs.map +1 -0
- package/dist/mcp.d.mts +84 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +241 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/messages.d.mts +183 -0
- package/dist/messages.d.mts.map +1 -0
- package/dist/messages.mjs +532 -0
- package/dist/messages.mjs.map +1 -0
- package/dist/microsoft/api.d.mts +47 -0
- package/dist/microsoft/api.d.mts.map +1 -0
- package/dist/microsoft/api.mjs +178 -0
- package/dist/microsoft/api.mjs.map +1 -0
- package/dist/microsoft/calendar.d.mts +30 -0
- package/dist/microsoft/calendar.d.mts.map +1 -0
- package/dist/microsoft/calendar.mjs +271 -0
- package/dist/microsoft/calendar.mjs.map +1 -0
- package/dist/microsoft/drive.d.mts +36 -0
- package/dist/microsoft/drive.d.mts.map +1 -0
- package/dist/microsoft/drive.mjs +298 -0
- package/dist/microsoft/drive.mjs.map +1 -0
- package/dist/microsoft/graph.d.mts +198 -0
- package/dist/microsoft/graph.d.mts.map +1 -0
- package/dist/microsoft/graph.mjs +264 -0
- package/dist/microsoft/graph.mjs.map +1 -0
- package/dist/microsoft/mail.d.mts +48 -0
- package/dist/microsoft/mail.d.mts.map +1 -0
- package/dist/microsoft/mail.mjs +488 -0
- package/dist/microsoft/mail.mjs.map +1 -0
- package/dist/microsoft/state.d.mts +480 -0
- package/dist/microsoft/state.d.mts.map +1 -0
- package/dist/microsoft/state.mjs +625 -0
- package/dist/microsoft/state.mjs.map +1 -0
- package/dist/microsoft.d.mts +168 -0
- package/dist/microsoft.d.mts.map +1 -0
- package/dist/microsoft.mjs +483 -0
- package/dist/microsoft.mjs.map +1 -0
- package/dist/node.d.mts +47 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +178 -0
- package/dist/node.mjs.map +1 -0
- package/dist/notion/api.d.mts +51 -0
- package/dist/notion/api.d.mts.map +1 -0
- package/dist/notion/api.mjs +507 -0
- package/dist/notion/api.mjs.map +1 -0
- package/dist/notion/state.d.mts +329 -0
- package/dist/notion/state.d.mts.map +1 -0
- package/dist/notion/state.mjs +432 -0
- package/dist/notion/state.mjs.map +1 -0
- package/dist/notion.d.mts +72 -0
- package/dist/notion.d.mts.map +1 -0
- package/dist/notion.mjs +127 -0
- package/dist/notion.mjs.map +1 -0
- package/dist/openai.d.mts +165 -0
- package/dist/openai.d.mts.map +1 -0
- package/dist/openai.mjs +141 -0
- package/dist/openai.mjs.map +1 -0
- package/dist/opencode-recordings.d.mts +7 -0
- package/dist/opencode-recordings.d.mts.map +1 -0
- package/dist/opencode-recordings.mjs +222 -0
- package/dist/opencode-recordings.mjs.map +1 -0
- package/dist/opencode.d.mts +70 -0
- package/dist/opencode.d.mts.map +1 -0
- package/dist/opencode.mjs +217 -0
- package/dist/opencode.mjs.map +1 -0
- package/dist/r2-fixtures.d.mts +36 -0
- package/dist/r2-fixtures.d.mts.map +1 -0
- package/dist/r2-fixtures.mjs +245 -0
- package/dist/r2-fixtures.mjs.map +1 -0
- package/dist/r2-guard.d.mts +43 -0
- package/dist/r2-guard.d.mts.map +1 -0
- package/dist/r2-guard.mjs +215 -0
- package/dist/r2-guard.mjs.map +1 -0
- package/dist/r2.d.mts +166 -0
- package/dist/r2.d.mts.map +1 -0
- package/dist/r2.mjs +517 -0
- package/dist/r2.mjs.map +1 -0
- package/dist/responses.d.mts +231 -0
- package/dist/responses.d.mts.map +1 -0
- package/dist/responses.mjs +556 -0
- package/dist/responses.mjs.map +1 -0
- package/dist/route-evidence.d.mts +45 -0
- package/dist/route-evidence.d.mts.map +1 -0
- package/dist/route-evidence.mjs +56 -0
- package/dist/route-evidence.mjs.map +1 -0
- package/dist/router.d.mts +93 -0
- package/dist/router.d.mts.map +1 -0
- package/dist/router.mjs +259 -0
- package/dist/router.mjs.map +1 -0
- package/dist/stateful-core.d.mts +17 -0
- package/dist/stateful-core.d.mts.map +1 -0
- package/dist/stateful-core.mjs +41 -0
- package/dist/stateful-core.mjs.map +1 -0
- package/dist/stateful-emulator.d.mts +565 -0
- package/dist/stateful-emulator.d.mts.map +1 -0
- package/dist/stateful-emulator.mjs +1228 -0
- package/dist/stateful-emulator.mjs.map +1 -0
- package/dist/stateful-secrets.d.mts +84 -0
- package/dist/stateful-secrets.d.mts.map +1 -0
- package/dist/stateful-secrets.mjs +216 -0
- package/dist/stateful-secrets.mjs.map +1 -0
- package/dist/subscription-usage-recordings.d.mts +9 -0
- package/dist/subscription-usage-recordings.d.mts.map +1 -0
- package/dist/subscription-usage-recordings.mjs +53 -0
- package/dist/subscription-usage-recordings.mjs.map +1 -0
- package/dist/subscription-usage.d.mts +53 -0
- package/dist/subscription-usage.d.mts.map +1 -0
- package/dist/subscription-usage.mjs +21 -0
- package/dist/subscription-usage.mjs.map +1 -0
- package/dist/telegram/api.d.mts +34 -0
- package/dist/telegram/api.d.mts.map +1 -0
- package/dist/telegram/api.mjs +257 -0
- package/dist/telegram/api.mjs.map +1 -0
- package/dist/telegram/state.d.mts +128 -0
- package/dist/telegram/state.d.mts.map +1 -0
- package/dist/telegram/state.mjs +154 -0
- package/dist/telegram/state.mjs.map +1 -0
- package/dist/telegram.d.mts +62 -0
- package/dist/telegram.d.mts.map +1 -0
- package/dist/telegram.mjs +127 -0
- package/dist/telegram.mjs.map +1 -0
- package/dist/todoist/api.d.mts +55 -0
- package/dist/todoist/api.d.mts.map +1 -0
- package/dist/todoist/api.mjs +415 -0
- package/dist/todoist/api.mjs.map +1 -0
- package/dist/todoist/state.d.mts +275 -0
- package/dist/todoist/state.d.mts.map +1 -0
- package/dist/todoist/state.mjs +345 -0
- package/dist/todoist/state.mjs.map +1 -0
- package/dist/todoist.d.mts +68 -0
- package/dist/todoist.d.mts.map +1 -0
- package/dist/todoist.mjs +181 -0
- package/dist/todoist.mjs.map +1 -0
- package/dist/xai.d.mts +267 -0
- package/dist/xai.d.mts.map +1 -0
- package/dist/xai.mjs +252 -0
- package/dist/xai.mjs.map +1 -0
- package/package.json +157 -0
- package/src/anthropic.ts +289 -0
- package/src/chat-completions.ts +924 -0
- package/src/codex.ts +296 -0
- package/src/dropbox/api.ts +1084 -0
- package/src/dropbox/state.ts +364 -0
- package/src/dropbox.ts +203 -0
- package/src/email-fixtures.ts +1297 -0
- package/src/email.ts +1108 -0
- package/src/emulator-compose.ts +147 -0
- package/src/emulator-http.ts +184 -0
- package/src/emulator-kernel.ts +844 -0
- package/src/fixture-route.ts +561 -0
- package/src/fortnox/api.ts +1352 -0
- package/src/fortnox/state.ts +798 -0
- package/src/fortnox.ts +801 -0
- package/src/gateway-evaluate-recordings.ts +153 -0
- package/src/gateway.ts +530 -0
- package/src/github/api.ts +986 -0
- package/src/github/state.ts +439 -0
- package/src/github.ts +271 -0
- package/src/google/calendar.ts +486 -0
- package/src/google/drive.ts +471 -0
- package/src/google/gmail.ts +1011 -0
- package/src/google/shared.ts +321 -0
- package/src/google/state.ts +686 -0
- package/src/google.ts +298 -0
- package/src/linkedin-search/api.ts +363 -0
- package/src/linkedin-search/state.ts +373 -0
- package/src/linkedin-search.ts +241 -0
- package/src/mcp/api.ts +995 -0
- package/src/mcp/recordings.ts +2665 -0
- package/src/mcp/state.ts +125 -0
- package/src/mcp.ts +319 -0
- package/src/messages.ts +844 -0
- package/src/microsoft/api.ts +426 -0
- package/src/microsoft/calendar.ts +491 -0
- package/src/microsoft/drive.ts +541 -0
- package/src/microsoft/graph.ts +550 -0
- package/src/microsoft/mail.ts +833 -0
- package/src/microsoft/state.ts +822 -0
- package/src/microsoft.ts +982 -0
- package/src/node.ts +293 -0
- package/src/notion/api.ts +976 -0
- package/src/notion/state.ts +512 -0
- package/src/notion.ts +210 -0
- package/src/openai.ts +198 -0
- package/src/opencode-recordings.ts +257 -0
- package/src/opencode.ts +271 -0
- package/src/r2-fixtures.ts +295 -0
- package/src/r2-guard.ts +323 -0
- package/src/r2.ts +890 -0
- package/src/responses.ts +901 -0
- package/src/route-evidence.ts +90 -0
- package/src/router.ts +490 -0
- package/src/stateful-core.ts +70 -0
- package/src/stateful-emulator.ts +2571 -0
- package/src/stateful-secrets.ts +299 -0
- package/src/subscription-usage-recordings.ts +77 -0
- package/src/subscription-usage.ts +68 -0
- package/src/telegram/api.ts +437 -0
- package/src/telegram/state.ts +229 -0
- package/src/telegram.ts +227 -0
- package/src/todoist/api.ts +736 -0
- package/src/todoist/state.ts +456 -0
- package/src/todoist.ts +316 -0
- package/src/xai.ts +341 -0
package/src/notion.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stateful Notion emulator (the `/v1` routes on `https://api.notion.com` at API version
|
|
3
|
+
* 2025-09-03), built on the upstream `@emulators/core` custom runtime, with a request ledger,
|
|
4
|
+
* status faults, and an `/_emulate/*` control plane.
|
|
5
|
+
*
|
|
6
|
+
* It never imports SDK code: its wire shapes, error envelopes, and default seed are copied as data
|
|
7
|
+
* from the synthetic Notion conformance fixtures, and every route names the conformance cases it
|
|
8
|
+
* follows in `notionEmulatorRoutes`. Only the routes those eight cases (with their cleanup) send
|
|
9
|
+
* are emulated. Response behaviour comes only from the fixtures: anything they do not show answers
|
|
10
|
+
* one ledgered 400 not-emulated, writes nothing, and uses up no fault. That includes a search
|
|
11
|
+
* without matches or matching a trashed page, so the read-only leftover lookup fails on a clean
|
|
12
|
+
* workspace and after the write case (the live runner prints its lookup-failed WARN). Search
|
|
13
|
+
* considers only the pages whose content the state holds (an implied page never matches) and
|
|
14
|
+
* refuses a match shown only as a query row. Every route answers only on the recorded origin
|
|
15
|
+
* `https://api.notion.com` (`fetchOn(origin)` serves it behind a loopback rewrite).
|
|
16
|
+
*
|
|
17
|
+
* Request-shape latitude (`/notion`, the only accepted deviations): any bearer value (never
|
|
18
|
+
* checked or stored); extra request headers; JSON key order; `content-type` media-type
|
|
19
|
+
* parameters; the order of query parameters; Notion ids with or without dashes, in any case; any
|
|
20
|
+
* search `query` (looked up in the state); and any `page_size` from 1 to 100 whose page shows
|
|
21
|
+
* only recorded results (the data source query: 1). `Notion-Version` must be `2025-09-03`.
|
|
22
|
+
* Everything else (other keys, filters, booleans, sorts, query parameters, another origin,
|
|
23
|
+
* titles, repeated or missing `page_size`, and cursors not issued for the same list since the
|
|
24
|
+
* last reset or whose list changed) is not emulated.
|
|
25
|
+
*
|
|
26
|
+
* Node-only: `@emulators/core` imports Node builtins, so the core is loaded lazily by
|
|
27
|
+
* `makeNotionEmulator` (importing this module has no side effects).
|
|
28
|
+
*
|
|
29
|
+
* @experimental
|
|
30
|
+
*/
|
|
31
|
+
import { Data, Predicate } from 'effect'
|
|
32
|
+
import {
|
|
33
|
+
notionApiRoutes,
|
|
34
|
+
notionEmulatorDrillKnobs,
|
|
35
|
+
notionEmulatorVersion,
|
|
36
|
+
type NotionApiEnv,
|
|
37
|
+
type NotionEmulatorDrills
|
|
38
|
+
} from './notion/api.ts'
|
|
39
|
+
import {
|
|
40
|
+
buildSeedState,
|
|
41
|
+
decodeState,
|
|
42
|
+
type NotionEmulatorSeed,
|
|
43
|
+
type NotionEmulatorState
|
|
44
|
+
} from './notion/state.ts'
|
|
45
|
+
import type { EmulatorRouteEvidence } from './route-evidence.ts'
|
|
46
|
+
import {
|
|
47
|
+
StatefulFault,
|
|
48
|
+
StatefulFaultMatch,
|
|
49
|
+
checkBooleanDrills,
|
|
50
|
+
makeStatefulEmulator,
|
|
51
|
+
routeEvidence,
|
|
52
|
+
type StatefulCoverage,
|
|
53
|
+
type StatefulEmulatorApi,
|
|
54
|
+
type StatefulFaultState,
|
|
55
|
+
type StatefulInputKind,
|
|
56
|
+
type StatefulLedgerEntry
|
|
57
|
+
} from './stateful-emulator.ts'
|
|
58
|
+
import { statefulCoreRuntime } from './stateful-core.ts'
|
|
59
|
+
|
|
60
|
+
export type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'
|
|
61
|
+
|
|
62
|
+
export { emulatorEvidenceHeader } from './route-evidence.ts'
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
notionEmulatorBasePath,
|
|
66
|
+
notionEmulatorOrigin,
|
|
67
|
+
notionEmulatorVersion,
|
|
68
|
+
type NotionEmulatorDrills
|
|
69
|
+
} from './notion/api.ts'
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
NotionEmulatorBlock,
|
|
73
|
+
NotionEmulatorBotUser,
|
|
74
|
+
NotionEmulatorDataSource,
|
|
75
|
+
NotionEmulatorDatabase,
|
|
76
|
+
NotionEmulatorImpliedPage,
|
|
77
|
+
NotionEmulatorPage,
|
|
78
|
+
NotionEmulatorParent,
|
|
79
|
+
NotionEmulatorProfile,
|
|
80
|
+
NotionEmulatorPropertyItems,
|
|
81
|
+
NotionEmulatorSeed,
|
|
82
|
+
NotionEmulatorStateSchema,
|
|
83
|
+
type NotionEmulatorState
|
|
84
|
+
} from './notion/state.ts'
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Route evidence manifest: every emulated route, whether it writes, and the conformance cases
|
|
88
|
+
* whose (currently synthetic, unverified) wire claims it follows. Kept in sync with the handlers
|
|
89
|
+
* by construction (both come from one route table).
|
|
90
|
+
*/
|
|
91
|
+
export const notionEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> =
|
|
92
|
+
notionApiRoutes.map(routeEvidence)
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Optional fault filter; an omitted field matches every request. `method` is the HTTP method;
|
|
96
|
+
* `path` is the raw request path (ending in `*`, a prefix); `route` is a route template of the
|
|
97
|
+
* manifest (another value is rejected when the fault is added).
|
|
98
|
+
*/
|
|
99
|
+
export const NotionFaultMatch = StatefulFaultMatch
|
|
100
|
+
|
|
101
|
+
export type NotionFaultMatch = typeof StatefulFaultMatch.Type
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A status fault (400-599): answer matching requests with this status, headers, and body instead
|
|
105
|
+
* of the route's write, so nothing is written. The body defaults to
|
|
106
|
+
* `{ error: { type: 'emulator_fault', message } }`; `count` limits how many requests it answers.
|
|
107
|
+
* Only a request the emulator would answer reaches a fault: a request that is not emulated, by
|
|
108
|
+
* its shape or by the state, never uses one up.
|
|
109
|
+
*/
|
|
110
|
+
export const NotionFault = StatefulFault
|
|
111
|
+
|
|
112
|
+
export type NotionFault = StatefulFault
|
|
113
|
+
|
|
114
|
+
export type NotionFaultState = StatefulFaultState
|
|
115
|
+
|
|
116
|
+
export type NotionLedgerEntry = StatefulLedgerEntry
|
|
117
|
+
|
|
118
|
+
export type NotionCoverage = StatefulCoverage
|
|
119
|
+
|
|
120
|
+
/** Invalid emulator input from the JS API: a seed, a fault, or an option. A programmer error. */
|
|
121
|
+
export class NotionEmulatorInputInvalid extends Data.TaggedError('NotionEmulatorInputInvalid')<{
|
|
122
|
+
readonly input: StatefulInputKind
|
|
123
|
+
readonly reason: string
|
|
124
|
+
}> {
|
|
125
|
+
override get message(): string {
|
|
126
|
+
return `Invalid Notion emulator ${this.input}: ${this.reason}`
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type NotionEmulatorOptions = {
|
|
131
|
+
/** Typed seed; defaults to the fixture entities (`profile: 'default'`). */
|
|
132
|
+
readonly seed?: NotionEmulatorSeed
|
|
133
|
+
/** Clock in epoch milliseconds (created page timestamps). Defaults to `Date.now`. */
|
|
134
|
+
readonly now?: () => number
|
|
135
|
+
/** Drill knobs (tests only): make the emulator disagree with one conformance claim. */
|
|
136
|
+
readonly drills?: NotionEmulatorDrills
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export type NotionEmulator = StatefulEmulatorApi<NotionEmulatorState, NotionEmulatorSeed>
|
|
140
|
+
|
|
141
|
+
const inputInvalid = (input: StatefulInputKind, reason: string) =>
|
|
142
|
+
new NotionEmulatorInputInvalid({ input, reason })
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Create a stateful Notion emulator on the `@emulators/core` custom runtime. Each call has its
|
|
146
|
+
* own state, ledger, and faults. Rejects with `NotionEmulatorInputInvalid` for an invalid seed or
|
|
147
|
+
* option. Every request needs `Notion-Version: 2025-09-03` (else not emulated). See
|
|
148
|
+
* `src/stateful-emulator.ts` for the request precedence.
|
|
149
|
+
*/
|
|
150
|
+
export const makeNotionEmulator = async (
|
|
151
|
+
options: NotionEmulatorOptions = {}
|
|
152
|
+
): Promise<NotionEmulator> => {
|
|
153
|
+
const initial = buildSeedState(options.seed ?? {})
|
|
154
|
+
|
|
155
|
+
if (Predicate.isString(initial)) {
|
|
156
|
+
throw inputInvalid('seed', initial)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
checkBooleanDrills(options.drills, notionEmulatorDrillKnobs, inputInvalid)
|
|
160
|
+
|
|
161
|
+
const drills = options.drills ?? {}
|
|
162
|
+
|
|
163
|
+
const env: NotionApiEnv = {
|
|
164
|
+
now: options.now ?? (() => Date.now()),
|
|
165
|
+
drills: {
|
|
166
|
+
searchRepeatsResults: drills.searchRepeatsResults === true,
|
|
167
|
+
botUserAsPerson: drills.botUserAsPerson === true,
|
|
168
|
+
envelopeStatusMismatch: drills.envelopeStatusMismatch === true,
|
|
169
|
+
omitTitlePlainText: drills.omitTitlePlainText === true,
|
|
170
|
+
blockCursorRepeats: drills.blockCursorRepeats === true,
|
|
171
|
+
rejectDoubleEncodedPropertyId: drills.rejectDoubleEncodedPropertyId === true,
|
|
172
|
+
rowParentAsDatabase: drills.rowParentAsDatabase === true,
|
|
173
|
+
trashedPageNotFound: drills.trashedPageNotFound === true
|
|
174
|
+
},
|
|
175
|
+
cursors: new Map(),
|
|
176
|
+
propertyCursorCounter: { next: 1 },
|
|
177
|
+
generation: { current: 0 },
|
|
178
|
+
firstIssued: new Map()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const api = await makeStatefulEmulator<NotionEmulatorState, NotionApiEnv, NotionEmulatorSeed>(
|
|
182
|
+
{
|
|
183
|
+
routes: notionApiRoutes,
|
|
184
|
+
env,
|
|
185
|
+
initial,
|
|
186
|
+
buildSeed: buildSeedState,
|
|
187
|
+
requestProblem: header =>
|
|
188
|
+
header('notion-version') === notionEmulatorVersion
|
|
189
|
+
? undefined
|
|
190
|
+
: `Notion-Version other than ${notionEmulatorVersion} (the version every fixture sends) is not emulated`,
|
|
191
|
+
recordHeaders: [{ name: 'notion-version', json: false }],
|
|
192
|
+
// Cursor values never cross a reset or seed: property cursor numbers keep advancing, and the
|
|
193
|
+
// generation makes later search and block cursors distinct from earlier ones.
|
|
194
|
+
clearRuntime: () => {
|
|
195
|
+
env.cursors.clear()
|
|
196
|
+
env.generation.current += 1
|
|
197
|
+
},
|
|
198
|
+
runtimeState: () => ({}),
|
|
199
|
+
seedSummary: state => ({
|
|
200
|
+
pages: state.pages.length,
|
|
201
|
+
blocks: state.blocks.length,
|
|
202
|
+
dataSources: state.dataSources.length
|
|
203
|
+
}),
|
|
204
|
+
inputInvalid
|
|
205
|
+
},
|
|
206
|
+
statefulCoreRuntime({ name: 'notion', initial, decodeState, inputInvalid })
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
return api
|
|
210
|
+
}
|
package/src/openai.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Chat Completions emulator: a plain fetch handler for
|
|
3
|
+
* `POST /v1/chat/completions` (origin `https://api.openai.com`), with scripted
|
|
4
|
+
* turns, wire faults, a request ledger, and an `/_emulate/*` control plane.
|
|
5
|
+
*
|
|
6
|
+
* It never imports SDK code: its wire shapes follow the synthetic OpenAI chat
|
|
7
|
+
* conformance fixtures (`chat.completion.chunk` SSE, usage chunk,
|
|
8
|
+
* `data: [DONE]`, the `chat.completion` JSON body, and the
|
|
9
|
+
* `{ error: { message, type, param, code } }` envelope) and are linked to
|
|
10
|
+
* those conformance case ids in `openAiEmulatorRoutes`. The Chat Completions
|
|
11
|
+
* machinery is shared with the Gateway emulator (`chat-completions.ts`).
|
|
12
|
+
*
|
|
13
|
+
* Reasoning models are not emulated yet: no default model streams reasoning,
|
|
14
|
+
* and scripted turns cannot carry reasoning.
|
|
15
|
+
*
|
|
16
|
+
* Runtime-portable Web APIs only (`Request`, `Response`, `ReadableStream`,
|
|
17
|
+
* `TextEncoder`, `URL`); no Effect runtime is required to use it.
|
|
18
|
+
*
|
|
19
|
+
* @experimental
|
|
20
|
+
*/
|
|
21
|
+
import { Data } from 'effect'
|
|
22
|
+
import * as Schema from 'effect/Schema'
|
|
23
|
+
import {
|
|
24
|
+
ChatFault,
|
|
25
|
+
ChatFaultMatch,
|
|
26
|
+
ChatScriptedError,
|
|
27
|
+
ChatScriptedToolCall,
|
|
28
|
+
ChatScriptedUsage,
|
|
29
|
+
chatScriptedCompletionFields,
|
|
30
|
+
makeChatCompletionsEmulator,
|
|
31
|
+
type ChatCompletionsEmulator,
|
|
32
|
+
type ChatCoverage,
|
|
33
|
+
type ChatFaultKind,
|
|
34
|
+
type ChatFaultState,
|
|
35
|
+
type ChatLedgerEntry,
|
|
36
|
+
type ChatRouteCoverage,
|
|
37
|
+
type ChatWireError
|
|
38
|
+
} from './chat-completions.ts'
|
|
39
|
+
import type { EmulatorRouteEvidence } from './route-evidence.ts'
|
|
40
|
+
|
|
41
|
+
export type { EmulatorEvidence, EmulatorRouteEvidence } from './route-evidence.ts'
|
|
42
|
+
|
|
43
|
+
export { emulatorEvidenceHeader } from './route-evidence.ts'
|
|
44
|
+
|
|
45
|
+
export const openAiChatCompletionsPath = '/v1/chat/completions'
|
|
46
|
+
|
|
47
|
+
/** Synthetic-safe default model ids, including the OpenAI chat conformance defaults. */
|
|
48
|
+
export const openAiEmulatorDefaultModels: ReadonlyArray<string> = ['gpt-4.1-nano', 'gpt-4.1-mini']
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Route evidence manifest: every emulated OpenAI route and the conformance
|
|
52
|
+
* cases whose (currently synthetic, unverified) wire shapes it follows.
|
|
53
|
+
*/
|
|
54
|
+
export const openAiEmulatorRoutes: ReadonlyArray<EmulatorRouteEvidence> = [
|
|
55
|
+
{
|
|
56
|
+
method: 'POST',
|
|
57
|
+
path: openAiChatCompletionsPath,
|
|
58
|
+
kind: 'provider',
|
|
59
|
+
write: false,
|
|
60
|
+
caseIds: [
|
|
61
|
+
'openai.chat.stream.plain-text',
|
|
62
|
+
'openai.chat.stream.tool-call-deltas',
|
|
63
|
+
'openai.chat.stream.error-envelope',
|
|
64
|
+
'openai.chat.json.plain-text'
|
|
65
|
+
],
|
|
66
|
+
evidence: 'unverified',
|
|
67
|
+
observedAt: undefined
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
/** Optional fault filter; an omitted field matches every request. `path` ending in `*` is a prefix. */
|
|
72
|
+
export const OpenAiFaultMatch = ChatFaultMatch
|
|
73
|
+
|
|
74
|
+
export type OpenAiFaultMatch = ChatFaultMatch
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Wire faults for emulated routes, the same kinds as the Gateway emulator:
|
|
78
|
+
* `status` (the body defaults to an OpenAI error envelope),
|
|
79
|
+
* `error-after-chunks`, and `truncate-after-chunks`. Statuses that cannot
|
|
80
|
+
* carry a body and redirects are rejected, as are invalid headers and
|
|
81
|
+
* `location`. A chunk fault that cannot take effect answers 500 and is not
|
|
82
|
+
* consumed.
|
|
83
|
+
*/
|
|
84
|
+
export const OpenAiFault = ChatFault
|
|
85
|
+
|
|
86
|
+
export type OpenAiFault = ChatFault
|
|
87
|
+
|
|
88
|
+
export type OpenAiFaultKind = ChatFaultKind
|
|
89
|
+
|
|
90
|
+
/** Usage for a scripted turn (sent as the wire `usage` object). */
|
|
91
|
+
export const OpenAiScriptedUsage = ChatScriptedUsage
|
|
92
|
+
|
|
93
|
+
export type OpenAiScriptedUsage = ChatScriptedUsage
|
|
94
|
+
|
|
95
|
+
/** One scripted tool call; its JSON arguments stream as these fragments. */
|
|
96
|
+
export const OpenAiScriptedToolCall = ChatScriptedToolCall
|
|
97
|
+
|
|
98
|
+
export type OpenAiScriptedToolCall = ChatScriptedToolCall
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* A scripted completion. Every field is exact (nothing is filled in) except:
|
|
102
|
+
* `usage` omitted is synthesized when the request asks for usage, and `null`
|
|
103
|
+
* drops it; `finishReason` omitted is `tool_calls` with tool calls, else
|
|
104
|
+
* `stop`. Reasoning is not emulated: reasoning fields are rejected.
|
|
105
|
+
*/
|
|
106
|
+
export const OpenAiScriptedCompletion = Schema.Struct(chatScriptedCompletionFields)
|
|
107
|
+
|
|
108
|
+
export type OpenAiScriptedCompletion = typeof OpenAiScriptedCompletion.Type
|
|
109
|
+
|
|
110
|
+
/** A scripted error response: status, body (a string is sent as is), and optional headers. */
|
|
111
|
+
export const OpenAiScriptedError = ChatScriptedError
|
|
112
|
+
|
|
113
|
+
export type OpenAiScriptedError = ChatScriptedError
|
|
114
|
+
|
|
115
|
+
/** A turn queued for the next chat completion request. */
|
|
116
|
+
export const OpenAiScriptedTurn = Schema.Union([OpenAiScriptedError, OpenAiScriptedCompletion])
|
|
117
|
+
|
|
118
|
+
export type OpenAiScriptedTurn = typeof OpenAiScriptedTurn.Type
|
|
119
|
+
|
|
120
|
+
/** Thrown by the JS API (`faults.add`, `script.enqueue`) for invalid input; a programmer error. */
|
|
121
|
+
export class OpenAiEmulatorInputInvalid extends Data.TaggedError('OpenAiEmulatorInputInvalid')<{
|
|
122
|
+
readonly input: 'fault' | 'turn'
|
|
123
|
+
readonly reason: string
|
|
124
|
+
}> {
|
|
125
|
+
override get message(): string {
|
|
126
|
+
return `Invalid OpenAI emulator ${this.input}: ${this.reason}`
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type OpenAiLedgerEntry = ChatLedgerEntry
|
|
131
|
+
|
|
132
|
+
export type OpenAiFaultState = ChatFaultState
|
|
133
|
+
|
|
134
|
+
export type OpenAiRouteCoverage = ChatRouteCoverage
|
|
135
|
+
|
|
136
|
+
export type OpenAiCoverage = ChatCoverage
|
|
137
|
+
|
|
138
|
+
export type OpenAiEmulatorOptions = {
|
|
139
|
+
/** Model ids that exist. Defaults to `openAiEmulatorDefaultModels`. */
|
|
140
|
+
readonly knownModels?: ReadonlyArray<string>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export type OpenAiEmulator = ChatCompletionsEmulator<OpenAiScriptedTurn>
|
|
144
|
+
|
|
145
|
+
const openAiErrorEnvelope = (error: ChatWireError): Schema.Json => ({
|
|
146
|
+
error: { message: error.message, type: error.type, param: null, code: error.code }
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create an OpenAI Chat Completions emulator. Each call has independent
|
|
151
|
+
* ledger, fault, and script state.
|
|
152
|
+
*
|
|
153
|
+
* Without a script, `POST /v1/chat/completions` answers a known model with
|
|
154
|
+
* synthetic text deltas (`stream: true`: `chat.completion.chunk` SSE, a finish
|
|
155
|
+
* chunk, a usage chunk when `stream_options.include_usage` is set, and
|
|
156
|
+
* `data: [DONE]`; `stream: false`: one `chat.completion` JSON body). A request
|
|
157
|
+
* with `tools` gets one tool call whose arguments are synthesized from the
|
|
158
|
+
* tool's JSON Schema and streamed in fragments, finishing with `tool_calls`.
|
|
159
|
+
* Unknown models get the OpenAI error envelope (404, `model_not_found`); a
|
|
160
|
+
* missing bearer credential gets a 401 envelope (`invalid_api_key`); unknown
|
|
161
|
+
* routes get a 404 JSON error. The ledger records the
|
|
162
|
+
* `max_completion_tokens` limit as `maxCompletionTokens`.
|
|
163
|
+
*
|
|
164
|
+
* Precedence per chat request matches the Gateway emulator: authentication and
|
|
165
|
+
* JSON validation, then the first matching `status` fault, then the next
|
|
166
|
+
* scripted turn, then model validation and defaults; a first matching chunk
|
|
167
|
+
* fault then shapes the body. Credential headers are never recorded, and the
|
|
168
|
+
* bearer value is never checked or stored.
|
|
169
|
+
*/
|
|
170
|
+
export const makeOpenAiEmulator = (options: OpenAiEmulatorOptions = {}): OpenAiEmulator =>
|
|
171
|
+
makeChatCompletionsEmulator({
|
|
172
|
+
path: openAiChatCompletionsPath,
|
|
173
|
+
routes: openAiEmulatorRoutes,
|
|
174
|
+
knownModels: options.knownModels ?? openAiEmulatorDefaultModels,
|
|
175
|
+
reasoningModels: undefined,
|
|
176
|
+
errorEnvelope: openAiErrorEnvelope,
|
|
177
|
+
// Copied data shape of the synthetic OpenAI error-envelope fixture (unknown model id).
|
|
178
|
+
unknownModel: {
|
|
179
|
+
status: 404,
|
|
180
|
+
error: {
|
|
181
|
+
message: 'Synthetic placeholder: the requested model does not exist.',
|
|
182
|
+
type: 'invalid_request_error',
|
|
183
|
+
code: 'model_not_found'
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
auth: {
|
|
187
|
+
unauthorized: {
|
|
188
|
+
message: 'Synthetic: missing or invalid API key.',
|
|
189
|
+
type: 'invalid_request_error',
|
|
190
|
+
code: 'invalid_api_key'
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
completionTokenField: 'max_completion_tokens',
|
|
194
|
+
responseIdPrefix: 'chatcmpl-synthetic',
|
|
195
|
+
defaultText: ['Hello', ' from the', ' synthetic OpenAI emulator.'],
|
|
196
|
+
turnSchema: OpenAiScriptedTurn,
|
|
197
|
+
inputInvalid: (input, reason) => new OpenAiEmulatorInputInvalid({ input, reason })
|
|
198
|
+
})
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Go recordings the `/opencode` emulator answers from: one per Go conformance fixture.
|
|
3
|
+
*
|
|
4
|
+
* Copied as data from the committed (synthetic, unverified) conformance fixtures; never imported.
|
|
5
|
+
* `test/fixture-recordings.test.ts` fails when a fixture changes and this copy does not. Internal;
|
|
6
|
+
* not a package export.
|
|
7
|
+
*/
|
|
8
|
+
import type { FixtureRecording } from './fixture-route.ts'
|
|
9
|
+
|
|
10
|
+
export const openCodeGoRecordings: ReadonlyArray<FixtureRecording> = [
|
|
11
|
+
{
|
|
12
|
+
fixtureId: 'opencode.go.chat.stream.plain-text.synthetic',
|
|
13
|
+
caseId: 'opencode.go.chat.stream.plain-text',
|
|
14
|
+
request: {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
path: '/zen/go/v1/chat/completions',
|
|
17
|
+
query: '',
|
|
18
|
+
headers: {
|
|
19
|
+
accept: 'text/event-stream',
|
|
20
|
+
'content-type': 'application/json'
|
|
21
|
+
},
|
|
22
|
+
body: {
|
|
23
|
+
model: 'synthetic-go-chat',
|
|
24
|
+
messages: [
|
|
25
|
+
{
|
|
26
|
+
role: 'system',
|
|
27
|
+
content: 'Reply in one short sentence.'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
role: 'user',
|
|
31
|
+
content: 'Say hello.'
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
max_tokens: 64,
|
|
35
|
+
stream: true,
|
|
36
|
+
stream_options: {
|
|
37
|
+
include_usage: true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
response: {
|
|
42
|
+
status: 200,
|
|
43
|
+
headers: {
|
|
44
|
+
'content-type': 'text/event-stream'
|
|
45
|
+
},
|
|
46
|
+
streamed: true,
|
|
47
|
+
chunks: [
|
|
48
|
+
'data: {"id":"chatcmpl-synthetic-go-1","object":"chat.completion.chunk","created":1790000000,"model":"synthetic-go-chat","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}\n\n',
|
|
49
|
+
'data: {"id":"chatcmpl-synthetic-go-1","object":"chat.completion.chunk","created":1790000000,"model":"synthetic-go-chat","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}\n\n',
|
|
50
|
+
'data: {"id":"chatcmpl-synthetic-go-1","object":"chat.completion.chunk","created":1790000000,"model":"synthetic-go-chat","choices":[{"index":0,',
|
|
51
|
+
'"delta":{"content":" from the"},"finish_reason":null}]}\n\n',
|
|
52
|
+
'data: {"id":"chatcmpl-synthetic-go-1","object":"chat.completion.chunk","created":1790000000,"model":"synthetic-go-chat","choices":[{"index":0,"delta":{"content":" synthetic model."},"finish_reason":null}]}\n\n',
|
|
53
|
+
'data: {"id":"chatcmpl-synthetic-go-1","object":"chat.completion.chunk","created":1790000000,"model":"synthetic-go-chat","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}\n\n',
|
|
54
|
+
'data: {"id":"chatcmpl-synthetic-go-1","object":"chat.completion.chunk","created":1790000000,"model":"synthetic-go-chat","choices":[],"usage":{"prompt_tokens":53,"completion_tokens":12,"total_tokens":65}}\n\n',
|
|
55
|
+
'data: [DONE]\n\n'
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
fixtureId: 'opencode.go.messages.stream.plain-text.synthetic',
|
|
61
|
+
caseId: 'opencode.go.messages.stream.plain-text',
|
|
62
|
+
request: {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
path: '/zen/go/v1/messages',
|
|
65
|
+
query: '',
|
|
66
|
+
headers: {
|
|
67
|
+
accept: 'text/event-stream',
|
|
68
|
+
'content-type': 'application/json'
|
|
69
|
+
},
|
|
70
|
+
body: {
|
|
71
|
+
model: 'synthetic-go-messages',
|
|
72
|
+
system: [
|
|
73
|
+
{
|
|
74
|
+
type: 'text',
|
|
75
|
+
text: 'Reply in one short sentence.'
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
messages: [
|
|
79
|
+
{
|
|
80
|
+
role: 'user',
|
|
81
|
+
content: 'Say hello.'
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
max_tokens: 64,
|
|
85
|
+
stream: true
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
response: {
|
|
89
|
+
status: 200,
|
|
90
|
+
headers: {
|
|
91
|
+
'content-type': 'text/event-stream'
|
|
92
|
+
},
|
|
93
|
+
streamed: true,
|
|
94
|
+
chunks: [
|
|
95
|
+
'event: message_start\ndata: {"type":"message_start","message":{"id":"msg_synthetic_1","type":"message","role":"assistant","model":"synthetic-go-messages","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":45,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":1}}}\n\n',
|
|
96
|
+
'event: content_block_start\ndata: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}\n\n',
|
|
97
|
+
'event: ping\ndata: {"type":"ping"}\n\n',
|
|
98
|
+
'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}\n\n',
|
|
99
|
+
'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta",',
|
|
100
|
+
'"text":" from the"}}\n\n',
|
|
101
|
+
'event: content_block_delta\ndata: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" synthetic Anthropic emulator."}}\n\n',
|
|
102
|
+
'event: content_block_stop\ndata: {"type":"content_block_stop","index":0}\n\n',
|
|
103
|
+
'event: message_delta\ndata: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":45,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":11}}\n\n',
|
|
104
|
+
'event: message_stop\ndata: {"type":"message_stop"}\n\n'
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
fixtureId: 'opencode.go.responses.stream.plain-text.synthetic',
|
|
110
|
+
caseId: 'opencode.go.responses.stream.plain-text',
|
|
111
|
+
request: {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
path: '/zen/go/v1/responses',
|
|
114
|
+
query: '',
|
|
115
|
+
headers: {
|
|
116
|
+
accept: 'text/event-stream',
|
|
117
|
+
'content-type': 'application/json'
|
|
118
|
+
},
|
|
119
|
+
body: {
|
|
120
|
+
model: 'synthetic-go-responses',
|
|
121
|
+
instructions: 'Reply in one short sentence.',
|
|
122
|
+
input: [
|
|
123
|
+
{
|
|
124
|
+
role: 'user',
|
|
125
|
+
content: 'Say hello.'
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
store: false,
|
|
129
|
+
stream: true,
|
|
130
|
+
max_output_tokens: 64
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
response: {
|
|
134
|
+
status: 200,
|
|
135
|
+
headers: {
|
|
136
|
+
'content-type': 'text/event-stream'
|
|
137
|
+
},
|
|
138
|
+
streamed: true,
|
|
139
|
+
chunks: [
|
|
140
|
+
'event: response.created\ndata: {"type":"response.created","sequence_number":0,"response":{"id":"resp_synthetic_1","object":"response","created_at":1790000000,"status":"in_progress","error":null,"incomplete_details":null,"model":"synthetic-go-responses","output":[],"usage":null}}\n\n',
|
|
141
|
+
'event: response.in_progress\ndata: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_synthetic_1","object":"response","created_at":1790000000,"status":"in_progress","error":null,"incomplete_details":null,"model":"synthetic-go-responses","output":[],"usage":null}}\n\n',
|
|
142
|
+
'event: response.output_item.added\ndata: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"msg_synthetic_1_0","type":"message","status":"in_progress","role":"assistant","content":[]}}\n\n',
|
|
143
|
+
'event: response.content_part.added\ndata: {"type":"response.content_part.added","sequence_number":3,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}\n\n',
|
|
144
|
+
'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"delta":"Hello"}\n\n',
|
|
145
|
+
'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","sequence_number":5,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,',
|
|
146
|
+
'"delta":" from the"}\n\n',
|
|
147
|
+
'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","sequence_number":6,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"delta":" synthetic model."}\n\n',
|
|
148
|
+
'event: response.output_text.done\ndata: {"type":"response.output_text.done","sequence_number":7,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"text":"Hello from the synthetic model."}\n\n',
|
|
149
|
+
'event: response.content_part.done\ndata: {"type":"response.content_part.done","sequence_number":8,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"part":{"type":"output_text","text":"Hello from the synthetic model.","annotations":[]}}\n\n',
|
|
150
|
+
'event: response.output_item.done\ndata: {"type":"response.output_item.done","sequence_number":9,"output_index":0,"item":{"id":"msg_synthetic_1_0","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hello from the synthetic model.","annotations":[]}]}}\n\n',
|
|
151
|
+
'event: response.completed\ndata: {"type":"response.completed","sequence_number":10,"response":{"id":"resp_synthetic_1","object":"response","created_at":1790000000,"status":"completed","error":null,"incomplete_details":null,"model":"synthetic-go-responses","output":[{"id":"msg_synthetic_1_0","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hello from the synthetic model.","annotations":[]}]}],"usage":{"input_tokens":45,"input_tokens_details":{"cached_tokens":0},"output_tokens":12,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":57}}}\n\n'
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
fixtureId: 'opencode.go.responses.stream.commentary-replay.synthetic',
|
|
157
|
+
caseId: 'opencode.go.responses.stream.commentary-replay',
|
|
158
|
+
request: {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
path: '/zen/go/v1/responses',
|
|
161
|
+
query: '',
|
|
162
|
+
headers: {
|
|
163
|
+
accept: 'text/event-stream',
|
|
164
|
+
'content-type': 'application/json'
|
|
165
|
+
},
|
|
166
|
+
body: {
|
|
167
|
+
model: 'synthetic-go-responses',
|
|
168
|
+
instructions: 'Reply in one short sentence.',
|
|
169
|
+
input: [
|
|
170
|
+
{
|
|
171
|
+
role: 'user',
|
|
172
|
+
content: 'What is the weather in Springfield?'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
role: 'assistant',
|
|
176
|
+
content: 'I will look up the weather first.',
|
|
177
|
+
phase: 'commentary'
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
type: 'function_call',
|
|
181
|
+
call_id: 'call_synthetic_weather',
|
|
182
|
+
name: 'lookup_weather',
|
|
183
|
+
arguments: '{"city":"Springfield"}'
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
type: 'function_call_output',
|
|
187
|
+
call_id: 'call_synthetic_weather',
|
|
188
|
+
output: 'Sunny and mild.'
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
store: false,
|
|
192
|
+
stream: true,
|
|
193
|
+
max_output_tokens: 64,
|
|
194
|
+
tools: [
|
|
195
|
+
{
|
|
196
|
+
type: 'function',
|
|
197
|
+
name: 'lookup_weather',
|
|
198
|
+
description: 'Look up the current weather for a city.',
|
|
199
|
+
parameters: {
|
|
200
|
+
type: 'object',
|
|
201
|
+
properties: {
|
|
202
|
+
city: {
|
|
203
|
+
type: 'string'
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
required: ['city'],
|
|
207
|
+
additionalProperties: false
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
],
|
|
211
|
+
parallel_tool_calls: true
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
response: {
|
|
215
|
+
status: 200,
|
|
216
|
+
headers: {
|
|
217
|
+
'content-type': 'text/event-stream'
|
|
218
|
+
},
|
|
219
|
+
streamed: true,
|
|
220
|
+
chunks: [
|
|
221
|
+
'event: response.created\ndata: {"type":"response.created","sequence_number":0,"response":{"id":"resp_synthetic_1","object":"response","created_at":1790000000,"status":"in_progress","error":null,"incomplete_details":null,"model":"synthetic-go-responses","output":[],"usage":null}}\n\n',
|
|
222
|
+
'event: response.in_progress\ndata: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_synthetic_1","object":"response","created_at":1790000000,"status":"in_progress","error":null,"incomplete_details":null,"model":"synthetic-go-responses","output":[],"usage":null}}\n\n',
|
|
223
|
+
'event: response.output_item.added\ndata: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"msg_synthetic_1_0","type":"message","status":"in_progress","role":"assistant","content":[]}}\n\n',
|
|
224
|
+
'event: response.content_part.added\ndata: {"type":"response.content_part.added","sequence_number":3,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}\n\n',
|
|
225
|
+
'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","sequence_number":4,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"delta":"It is sunny"}\n\n',
|
|
226
|
+
'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","sequence_number":5,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,',
|
|
227
|
+
'"delta":" and mild in Springfield."}\n\n',
|
|
228
|
+
'event: response.output_text.done\ndata: {"type":"response.output_text.done","sequence_number":6,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"text":"It is sunny and mild in Springfield."}\n\n',
|
|
229
|
+
'event: response.content_part.done\ndata: {"type":"response.content_part.done","sequence_number":7,"item_id":"msg_synthetic_1_0","output_index":0,"content_index":0,"part":{"type":"output_text","text":"It is sunny and mild in Springfield.","annotations":[]}}\n\n',
|
|
230
|
+
'event: response.output_item.done\ndata: {"type":"response.output_item.done","sequence_number":8,"output_index":0,"item":{"id":"msg_synthetic_1_0","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"It is sunny and mild in Springfield.","annotations":[]}]}}\n\n',
|
|
231
|
+
'event: response.completed\ndata: {"type":"response.completed","sequence_number":9,"response":{"id":"resp_synthetic_1","object":"response","created_at":1790000000,"status":"completed","error":null,"incomplete_details":null,"model":"synthetic-go-responses","output":[{"id":"msg_synthetic_1_0","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"It is sunny and mild in Springfield.","annotations":[]}]}],"usage":{"input_tokens":193,"input_tokens_details":{"cached_tokens":0},"output_tokens":9,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":202}}}\n\n'
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
fixtureId: 'opencode.go.usage.snapshot.synthetic',
|
|
237
|
+
caseId: 'opencode.go.usage.snapshot',
|
|
238
|
+
request: {
|
|
239
|
+
method: 'GET',
|
|
240
|
+
path: '/zen/go/v1/usage',
|
|
241
|
+
query: '',
|
|
242
|
+
headers: {
|
|
243
|
+
accept: 'application/json'
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
response: {
|
|
247
|
+
status: 200,
|
|
248
|
+
headers: {
|
|
249
|
+
'content-type': 'application/json'
|
|
250
|
+
},
|
|
251
|
+
streamed: false,
|
|
252
|
+
chunks: [
|
|
253
|
+
'{"usage":{"rolling":{"percent":12.5,"resetsAt":"2026-10-01T03:00:00.000Z"},"weekly":{"percent":40,"resetsAt":"2026-10-05T00:00:00.000Z"},"monthly":{"percent":55,"resetsAt":"2026-10-31T00:00:00.000Z"}}}'
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
]
|