@rynfar/meridian 1.72.0 → 1.74.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.
Files changed (177) hide show
  1. package/README.md +119 -107
  2. package/assets/banner.svg +18 -43
  3. package/assets/how-it-works.svg +40 -86
  4. package/assets/providers/README.md +10 -0
  5. package/assets/providers/antigravity.png +0 -0
  6. package/assets/providers/claude.png +0 -0
  7. package/dist/antigravity-clients/opencode.js +173 -0
  8. package/dist/antigravity-clients/pi.js +21 -0
  9. package/dist/antigravitySetup-35pd5xqn.js +191 -0
  10. package/dist/cli-1jstkdj9.js +30 -0
  11. package/dist/{cli-9e5cxp89.js → cli-2h3w5n6g.js} +163 -65
  12. package/dist/cli-3zwfvexc.js +56 -0
  13. package/dist/{cli-hxxy0m1z.js → cli-82e2m44k.js} +2 -1
  14. package/dist/cli-8sw5wr9j.js +136 -0
  15. package/dist/{cli-khhjyk04.js → cli-chsp880k.js} +214 -21
  16. package/dist/cli-dh0a1bw4.js +619 -0
  17. package/dist/cli-eehk3052.js +57 -0
  18. package/dist/{cli-ab7zkp1a.js → cli-fveyj34s.js} +32261 -18952
  19. package/dist/cli-grfp0s1j.js +330 -0
  20. package/dist/cli-qpfky8wy.js +133 -0
  21. package/dist/{cli-hj61zpt7.js → cli-wbsmhxcx.js} +26 -2
  22. package/dist/cli.js +111 -38
  23. package/dist/cliDashboard-c0dvkj4y.js +325 -0
  24. package/dist/configDir.d.ts +30 -0
  25. package/dist/configDir.d.ts.map +1 -0
  26. package/dist/organizationName-90f0atkn.js +29 -0
  27. package/dist/{pluginPage-f5gdmbxc.js → pluginPage-pp72d2j8.js} +1 -1
  28. package/dist/{profileCli-1eecbmgk.js → profileCli-1bfgc05h.js} +13 -3
  29. package/dist/{profilePage-mamej7kp.js → profilePage-vs319rvf.js} +252 -60
  30. package/dist/profileRename-4a4km6g8.js +22 -0
  31. package/dist/{profiles-wch9h234.js → profiles-0mp32ek9.js} +16 -3
  32. package/dist/proxy/adapterInstances.d.ts +1 -1
  33. package/dist/proxy/adapterInstances.d.ts.map +1 -1
  34. package/dist/proxy/adapters/claudecode.d.ts.map +1 -1
  35. package/dist/proxy/adapters/droid.d.ts.map +1 -1
  36. package/dist/proxy/auth.d.ts +2 -0
  37. package/dist/proxy/auth.d.ts.map +1 -1
  38. package/dist/proxy/authDiscovery.d.ts +52 -0
  39. package/dist/proxy/authDiscovery.d.ts.map +1 -0
  40. package/dist/proxy/backends/antigravity.d.ts +8 -0
  41. package/dist/proxy/backends/antigravity.d.ts.map +1 -0
  42. package/dist/proxy/backends/antigravityAttachments.d.ts +16 -0
  43. package/dist/proxy/backends/antigravityAttachments.d.ts.map +1 -0
  44. package/dist/proxy/backends/antigravityGrammar.d.ts +12 -0
  45. package/dist/proxy/backends/antigravityGrammar.d.ts.map +1 -0
  46. package/dist/proxy/backends/antigravityJobs.d.ts +19 -0
  47. package/dist/proxy/backends/antigravityJobs.d.ts.map +1 -0
  48. package/dist/proxy/backends/antigravityMedia.d.ts +9 -0
  49. package/dist/proxy/backends/antigravityMedia.d.ts.map +1 -0
  50. package/dist/proxy/backends/antigravityNative.d.ts +8 -0
  51. package/dist/proxy/backends/antigravityNative.d.ts.map +1 -0
  52. package/dist/proxy/backends/antigravityOpenai.d.ts +9 -0
  53. package/dist/proxy/backends/antigravityOpenai.d.ts.map +1 -0
  54. package/dist/proxy/backends/antigravityOpenaiMedia.d.ts +9 -0
  55. package/dist/proxy/backends/antigravityOpenaiMedia.d.ts.map +1 -0
  56. package/dist/proxy/backends/antigravityPlugins.d.ts +41 -0
  57. package/dist/proxy/backends/antigravityPlugins.d.ts.map +1 -0
  58. package/dist/proxy/backends/antigravityProbe.d.ts +16 -0
  59. package/dist/proxy/backends/antigravityProbe.d.ts.map +1 -0
  60. package/dist/proxy/backends/antigravityProcess.d.ts +4 -0
  61. package/dist/proxy/backends/antigravityProcess.d.ts.map +1 -0
  62. package/dist/proxy/backends/antigravityProtocol.d.ts +582 -0
  63. package/dist/proxy/backends/antigravityProtocol.d.ts.map +1 -0
  64. package/dist/proxy/backends/antigravityReplay.d.ts +41 -0
  65. package/dist/proxy/backends/antigravityReplay.d.ts.map +1 -0
  66. package/dist/proxy/backends/antigravityResponses.d.ts +35 -0
  67. package/dist/proxy/backends/antigravityResponses.d.ts.map +1 -0
  68. package/dist/proxy/backends/antigravityRuntime.d.ts +189 -0
  69. package/dist/proxy/backends/antigravityRuntime.d.ts.map +1 -0
  70. package/dist/proxy/backends/antigravitySchema.d.ts +10 -0
  71. package/dist/proxy/backends/antigravitySchema.d.ts.map +1 -0
  72. package/dist/proxy/backends/antigravitySessions.d.ts +28 -0
  73. package/dist/proxy/backends/antigravitySessions.d.ts.map +1 -0
  74. package/dist/proxy/backends/antigravityState.d.ts +22 -0
  75. package/dist/proxy/backends/antigravityState.d.ts.map +1 -0
  76. package/dist/proxy/backends/antigravityStops.d.ts +10 -0
  77. package/dist/proxy/backends/antigravityStops.d.ts.map +1 -0
  78. package/dist/proxy/backends/antigravityTokens.d.ts +14 -0
  79. package/dist/proxy/backends/antigravityTokens.d.ts.map +1 -0
  80. package/dist/proxy/backends/antigravityUrl.d.ts +7 -0
  81. package/dist/proxy/backends/antigravityUrl.d.ts.map +1 -0
  82. package/dist/proxy/backends/providerStatus.d.ts +15 -0
  83. package/dist/proxy/backends/providerStatus.d.ts.map +1 -0
  84. package/dist/proxy/credentialsMode.d.ts +77 -0
  85. package/dist/proxy/credentialsMode.d.ts.map +1 -0
  86. package/dist/proxy/design.d.ts.map +1 -1
  87. package/dist/proxy/followActive.d.ts +262 -0
  88. package/dist/proxy/followActive.d.ts.map +1 -0
  89. package/dist/proxy/limitDetection.d.ts +110 -0
  90. package/dist/proxy/limitDetection.d.ts.map +1 -0
  91. package/dist/proxy/models.d.ts.map +1 -1
  92. package/dist/proxy/oauthPlan.d.ts +63 -0
  93. package/dist/proxy/oauthPlan.d.ts.map +1 -0
  94. package/dist/proxy/oauthUsage.d.ts +65 -2
  95. package/dist/proxy/oauthUsage.d.ts.map +1 -1
  96. package/dist/proxy/openai.d.ts +6 -2
  97. package/dist/proxy/openai.d.ts.map +1 -1
  98. package/dist/proxy/organizationName.d.ts +71 -0
  99. package/dist/proxy/organizationName.d.ts.map +1 -0
  100. package/dist/proxy/passthroughEarlyStop.d.ts +10 -0
  101. package/dist/proxy/passthroughEarlyStop.d.ts.map +1 -1
  102. package/dist/proxy/planAllowance.d.ts +75 -0
  103. package/dist/proxy/planAllowance.d.ts.map +1 -0
  104. package/dist/proxy/probes.d.ts +85 -0
  105. package/dist/proxy/probes.d.ts.map +1 -0
  106. package/dist/proxy/profileCli.d.ts +53 -1
  107. package/dist/proxy/profileCli.d.ts.map +1 -1
  108. package/dist/proxy/profileHealth.d.ts +127 -0
  109. package/dist/proxy/profileHealth.d.ts.map +1 -0
  110. package/dist/proxy/profileRename.d.ts +83 -0
  111. package/dist/proxy/profileRename.d.ts.map +1 -0
  112. package/dist/proxy/profiles.d.ts +72 -6
  113. package/dist/proxy/profiles.d.ts.map +1 -1
  114. package/dist/proxy/query.d.ts +7 -0
  115. package/dist/proxy/query.d.ts.map +1 -1
  116. package/dist/proxy/routing.d.ts +59 -1
  117. package/dist/proxy/routing.d.ts.map +1 -1
  118. package/dist/proxy/sdkFeatures.d.ts +1 -1
  119. package/dist/proxy/sdkFeatures.d.ts.map +1 -1
  120. package/dist/proxy/server.d.ts.map +1 -1
  121. package/dist/proxy/session/fingerprint.d.ts +5 -0
  122. package/dist/proxy/session/fingerprint.d.ts.map +1 -1
  123. package/dist/proxy/session/lineage.d.ts.map +1 -1
  124. package/dist/proxy/setup.d.ts.map +1 -1
  125. package/dist/proxy/supervision.d.ts +41 -0
  126. package/dist/proxy/supervision.d.ts.map +1 -0
  127. package/dist/proxy/tokenRefresh.d.ts +38 -4
  128. package/dist/proxy/tokenRefresh.d.ts.map +1 -1
  129. package/dist/proxy/types.d.ts +32 -0
  130. package/dist/proxy/types.d.ts.map +1 -1
  131. package/dist/server.js +11 -7
  132. package/dist/settings.d.ts +83 -0
  133. package/dist/settings.d.ts.map +1 -0
  134. package/dist/{setup-ndmjpy23.js → setup-7fr49z92.js} +1 -1
  135. package/dist/statusProbe-9x9q261e.js +151 -0
  136. package/dist/telemetry/dashboard.d.ts.map +1 -1
  137. package/dist/telemetry/index.d.ts +20 -2
  138. package/dist/telemetry/index.d.ts.map +1 -1
  139. package/dist/telemetry/landing.d.ts +1 -1
  140. package/dist/telemetry/landing.d.ts.map +1 -1
  141. package/dist/telemetry/logStore.d.ts +11 -0
  142. package/dist/telemetry/logStore.d.ts.map +1 -1
  143. package/dist/telemetry/pricingStore.d.ts +2 -2
  144. package/dist/telemetry/pricingStore.d.ts.map +1 -1
  145. package/dist/telemetry/profileBar.d.ts +3 -3
  146. package/dist/telemetry/profileBar.d.ts.map +1 -1
  147. package/dist/telemetry/profileFacts.d.ts +17 -0
  148. package/dist/telemetry/profileFacts.d.ts.map +1 -0
  149. package/dist/telemetry/profileOrder.d.ts +68 -0
  150. package/dist/telemetry/profileOrder.d.ts.map +1 -0
  151. package/dist/telemetry/profilePage.d.ts.map +1 -1
  152. package/dist/telemetry/profileSort.d.ts +35 -0
  153. package/dist/telemetry/profileSort.d.ts.map +1 -0
  154. package/dist/telemetry/profileSpent.d.ts +68 -0
  155. package/dist/telemetry/profileSpent.d.ts.map +1 -0
  156. package/dist/telemetry/providerPage.d.ts +2 -0
  157. package/dist/telemetry/providerPage.d.ts.map +1 -0
  158. package/dist/telemetry/providerSetup.d.ts +5 -0
  159. package/dist/telemetry/providerSetup.d.ts.map +1 -0
  160. package/dist/telemetry/providerView.d.ts +45 -0
  161. package/dist/telemetry/providerView.d.ts.map +1 -0
  162. package/dist/telemetry/routeChain.d.ts +32 -0
  163. package/dist/telemetry/routeChain.d.ts.map +1 -0
  164. package/dist/telemetry/routes.d.ts.map +1 -1
  165. package/dist/telemetry/settingsPage.d.ts +1 -1
  166. package/dist/telemetry/settingsPage.d.ts.map +1 -1
  167. package/dist/telemetry/sqlite.d.ts.map +1 -1
  168. package/dist/telemetry/store.d.ts +6 -1
  169. package/dist/telemetry/store.d.ts.map +1 -1
  170. package/dist/telemetry/types.d.ts +132 -2
  171. package/dist/telemetry/types.d.ts.map +1 -1
  172. package/dist/{tokenRefresh-kzz08kea.js → tokenRefresh-eg9h83wd.js} +6 -1
  173. package/package.json +11 -4
  174. package/dist/cli-5jxyma6z.js +0 -277
  175. package/dist/cli-vj9cv18n.js +0 -38
  176. package/dist/proxy/settings.d.ts +0 -30
  177. package/dist/proxy/settings.d.ts.map +0 -1
@@ -0,0 +1,173 @@
1
+ // OpenCode V1 plugin: use a provider named meridian-agy.
2
+ import { createHash } from 'node:crypto'
3
+ export default async function ({ client }) {
4
+ return {
5
+ config: async config => {
6
+ const provider = config.provider?.['meridian-agy']
7
+ if (!provider) return
8
+ const options = provider.options ??= {}
9
+ const upstream = options.fetch ?? globalThis.fetch
10
+ options.fetch = (input, init) => streamSafely(upstream, input, init)
11
+ },
12
+ 'chat.headers': async (input, output) => {
13
+ if (input.model.providerID !== 'meridian-agy') return
14
+ for (const key of Object.keys(output.headers)) if (key.toLowerCase() === 'idempotency-key') delete output.headers[key]
15
+ // The header hook runs again on a processor retry. A random ID here
16
+ // would regenerate the response. Use the public client's active message,
17
+ // created before generation and retained across retries of that step.
18
+ const result = await client.session.messages({ path: { id: input.sessionID } })
19
+ if (result.error || !Array.isArray(result.data)) throw new Error('Cannot read OpenCode request identity')
20
+ const active = result.data.filter(({ info }) => info.role === 'assistant' && info.parentID === input.message.id && info.agent === input.agent && info.modelID === input.model.id && info.providerID === input.model.providerID && !info.time?.completed)
21
+ // Hidden one-shots and ambiguous concurrent steps must not share an ID.
22
+ if (active.length !== 1) return
23
+ output.headers['idempotency-key'] = createHash('sha256').update(JSON.stringify([input.sessionID, active[0].info.id])).digest('hex')
24
+ },
25
+ }
26
+ }
27
+
28
+ class DeliveryInterrupted extends Error {}
29
+ const maxBytes = 4 * 1024 * 1024
30
+ const encoder = new TextEncoder()
31
+ const wire = event => encoder.encode(`event: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`)
32
+
33
+ async function streamSafely(upstream, input, init) {
34
+ const abort = new AbortController()
35
+ const originalSignal = init?.signal ?? (input instanceof Request ? input.signal : undefined)
36
+ const signal = AbortSignal.any([abort.signal, AbortSignal.timeout(300000), ...(originalSignal ? [originalSignal] : [])])
37
+ const response = await upstream(input, { ...init, signal })
38
+ if (!response.ok || !response.body || !response.headers.get('content-type')?.includes('text/event-stream')) return response
39
+ const readers = new Set()
40
+ const cancelReaders = () => { for (const reader of readers) void reader.cancel(signal.reason).catch(() => undefined) }
41
+ signal.addEventListener('abort', cancelReaders, { once: true })
42
+ let cancelled = false
43
+ const displayed = new Map()
44
+ let message
45
+ async function* chunks(response) {
46
+ const reader = response.body.getReader()
47
+ readers.add(reader)
48
+ let bytes = 0
49
+ try {
50
+ signal.throwIfAborted()
51
+ while (true) {
52
+ let next
53
+ try { next = await reader.read() }
54
+ catch (error) { signal.throwIfAborted(); throw new DeliveryInterrupted(String(error)) }
55
+ signal.throwIfAborted()
56
+ if (next.done) break
57
+ bytes += next.value.byteLength
58
+ if (bytes > maxBytes) throw new Error('Meridian response exceeds the 4 MiB client buffer')
59
+ yield next.value
60
+ }
61
+ } finally {
62
+ readers.delete(reader)
63
+ // Preserve an original read failure if cancellation sees an errored reader.
64
+ await reader.cancel().catch(() => undefined)
65
+ reader.releaseLock()
66
+ }
67
+ }
68
+ async function* events(response) {
69
+ const decoder = new TextDecoder()
70
+ let pending = ''
71
+ for await (const chunk of chunks(response)) {
72
+ pending += decoder.decode(chunk, { stream: true })
73
+ let match
74
+ while ((match = /\r?\n\r?\n/.exec(pending))) {
75
+ const frame = pending.slice(0, match.index)
76
+ pending = pending.slice(match.index + match[0].length)
77
+ const data = frame.split(/\r?\n/).filter(line => line.startsWith('data:')).map(line => line.slice(5).trimStart()).join('\n')
78
+ if (data) yield JSON.parse(data)
79
+ }
80
+ }
81
+ pending += decoder.decode()
82
+ if (pending.trim()) throw new DeliveryInterrupted('Meridian stream ended within an event')
83
+ }
84
+ async function recover() {
85
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined))
86
+ if (typeof init?.body !== 'string') throw new Error('Meridian cannot recover a non-replayable request body')
87
+ const body = JSON.parse(init.body)
88
+ if (!headers.get('idempotency-key') && !body.meridian_request_id) throw new Error('Meridian recovery requires a request identity')
89
+ headers.set('x-meridian-replay-only', 'true')
90
+ headers.set('content-type', 'application/json')
91
+ headers.set('accept', 'application/json')
92
+ const saved = await upstream(input, { ...init, signal, headers, body: JSON.stringify({ ...body, stream: false }) })
93
+ if (!saved.ok || saved.headers.get('x-meridian-response-replayed') !== 'true') {
94
+ await saved.body?.cancel()
95
+ throw new Error(`Meridian saved-response recovery unavailable (${saved.status}); no tool calls released`)
96
+ }
97
+ const decoder = new TextDecoder()
98
+ let json = ''
99
+ for await (const chunk of chunks(saved)) json += decoder.decode(chunk, { stream: true })
100
+ const answer = JSON.parse(json + decoder.decode())
101
+ if (!answer.id || (message && answer.id !== message.id) || !Array.isArray(answer.content)) throw new Error('Meridian saved response identity changed')
102
+ // Validate every visible prefix before exposing any recovered suffix or tool.
103
+ for (const [index, shown] of displayed) {
104
+ const block = answer.content[index]
105
+ if (block?.type !== 'text' || typeof block.text !== 'string' || !block.text.startsWith(shown.text) || (shown.closed && block.text !== shown.text)) throw new Error('Meridian saved response differs from displayed text')
106
+ }
107
+ for (const block of answer.content) if (!['text', 'tool_use'].includes(block.type)) throw new Error('Meridian saved response contains an unsupported block')
108
+ const result = []
109
+ if (!message) result.push({ type: 'message_start', message: { ...answer, content: [], stop_reason: null, stop_sequence: null, usage: { ...answer.usage, output_tokens: 0 } } })
110
+ for (const [index, block] of answer.content.entries()) {
111
+ const shown = displayed.get(index)
112
+ if (shown?.closed) continue
113
+ if (!shown) result.push({ type: 'content_block_start', index, content_block: block.type === 'text' ? { type: 'text', text: '' } : { ...block, input: {} } })
114
+ const text = block.type === 'text' ? block.text.slice(shown?.text.length ?? 0) : JSON.stringify(block.input)
115
+ for (let offset = 0; offset < text.length; offset += 4096) result.push({ type: 'content_block_delta', index, delta: block.type === 'text' ? { type: 'text_delta', text: text.slice(offset, offset + 4096) } : { type: 'input_json_delta', partial_json: text.slice(offset, offset + 4096) } })
116
+ result.push({ type: 'content_block_stop', index })
117
+ }
118
+ result.push({ type: 'message_delta', delta: { stop_reason: answer.stop_reason, stop_sequence: answer.stop_sequence }, usage: answer.usage }, { type: 'message_stop' })
119
+ return result
120
+ }
121
+ const stream = new ReadableStream({
122
+ async start(controller) {
123
+ try {
124
+ const held = []
125
+ let holding = false, stopped = false
126
+ try {
127
+ for await (const event of events(response)) {
128
+ if (stopped) throw new Error('Meridian sent data after message_stop')
129
+ if (event.type === 'error') throw new Error(event.error?.message || 'Meridian stream failed')
130
+ if (event.type === 'ping') continue
131
+ if (event.type === 'message_start') {
132
+ if (message || !event.message?.id) throw new Error('Invalid Meridian message identity')
133
+ message = event.message
134
+ }
135
+ if (event.type === 'content_block_start' && event.content_block?.type !== 'text') holding = true
136
+ if (event.type === 'message_delta' || event.type === 'message_stop') holding = true
137
+ if (event.type === 'message_stop') stopped = true
138
+ if (holding) held.push(event)
139
+ else {
140
+ if (event.type === 'content_block_start') displayed.set(event.index, { text: event.content_block.text || '', closed: false })
141
+ if (event.type === 'content_block_delta') {
142
+ const block = displayed.get(event.index)
143
+ if (!block || event.delta?.type !== 'text_delta') throw new Error('Invalid Meridian text delta')
144
+ block.text += event.delta.text
145
+ }
146
+ if (event.type === 'content_block_stop') {
147
+ const block = displayed.get(event.index)
148
+ if (!block) throw new Error('Invalid Meridian text block')
149
+ block.closed = true
150
+ }
151
+ controller.enqueue(wire(event))
152
+ }
153
+ }
154
+ if (!stopped) throw new DeliveryInterrupted('Meridian stream ended before message_stop')
155
+ for (const event of held) controller.enqueue(wire(event))
156
+ } catch (error) {
157
+ signal.throwIfAborted()
158
+ if (!(error instanceof DeliveryInterrupted)) throw error
159
+ for (const event of await recover()) controller.enqueue(wire(event))
160
+ }
161
+ controller.close()
162
+ } catch (error) {
163
+ if (!cancelled) controller.error(error)
164
+ } finally {
165
+ signal.removeEventListener('abort', cancelReaders)
166
+ }
167
+ },
168
+ cancel(reason) { cancelled = true; abort.abort(reason) },
169
+ })
170
+ const headers = new Headers(response.headers)
171
+ headers.delete('content-encoding'); headers.delete('content-length')
172
+ return new Response(stream, { status: response.status, statusText: response.statusText, headers })
173
+ }
@@ -0,0 +1,21 @@
1
+ // Load with Pi's -e option, using a provider named meridian-agy.
2
+ import { createHash, randomUUID } from 'node:crypto'
3
+ export default function (pi) {
4
+ let pending
5
+ const reset = () => { pending = undefined }
6
+ // A new prompt or session operation is a new intent, even with identical text.
7
+ for (const event of ['before_agent_start', 'session_start', 'session_shutdown', 'session_before_switch', 'session_before_fork', 'session_before_tree', 'session_before_compact']) pi.on(event, reset)
8
+ pi.on('turn_end', event => {
9
+ if (event.message.stopReason === 'error' && pending) pending.retry = true
10
+ else reset()
11
+ })
12
+ pi.on('before_provider_request', (event, ctx) => {
13
+ if (ctx.model?.provider !== 'meridian-agy') { reset(); return }
14
+ const fingerprint = createHash('sha256').update(JSON.stringify(event.payload)).digest('hex')
15
+ // Pi removes a failed assistant message before its agent-level retry.
16
+ // Keep the ID only for that exact request; never cache conversation content.
17
+ if (!pending?.retry || pending.fingerprint !== fingerprint) pending = { fingerprint, id: randomUUID(), retry: false }
18
+ pending.retry = false
19
+ return { ...event.payload, meridian_request_id: pending.id }
20
+ })
21
+ }
@@ -0,0 +1,191 @@
1
+ import {
2
+ findOpencodeConfigPath
3
+ } from "./cli-82e2m44k.js";
4
+ import"./cli-p9swy5t3.js";
5
+
6
+ // src/proxy/antigravitySetup.ts
7
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
8
+ import { dirname, join, resolve } from "node:path";
9
+ import { homedir } from "node:os";
10
+ import { fileURLToPath } from "node:url";
11
+ import { randomUUID } from "node:crypto";
12
+ import { applyEdits, modify, parse } from "jsonc-parser";
13
+ var marker = `// Managed by meridian setup --antigravity.
14
+ `;
15
+ var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
16
+ function record(value, label) {
17
+ if (value === undefined)
18
+ return {};
19
+ if (!object(value))
20
+ throw new Error(`${label} must be an object; configuration was left untouched`);
21
+ return value;
22
+ }
23
+ function readConfig(path, client) {
24
+ const text = existsSync(path) ? readFileSync(path, "utf8") : `{}
25
+ `;
26
+ const errors = [];
27
+ const config = parse(text, errors, { allowTrailingComma: client === "opencode", disallowComments: client === "pi" });
28
+ if (errors.length || !object(config))
29
+ throw new Error(`Cannot parse ${path}; configuration was left untouched`);
30
+ return { text, config };
31
+ }
32
+ function parseAntigravitySetupArgs(args) {
33
+ const values = new Map;
34
+ const flags = new Set;
35
+ for (let index = 0;index < args.length; index++) {
36
+ const key = args[index];
37
+ if (key === "--antigravity" || key === "--set-default") {
38
+ flags.add(key);
39
+ continue;
40
+ }
41
+ if (!["--client", "--url", "--model", "--config-dir", "--api-key-env"].includes(key))
42
+ throw new Error(`Unknown Antigravity setup option: ${key}`);
43
+ const value = args[++index];
44
+ if (!value || value.startsWith("--") || values.has(key))
45
+ throw new Error(`${key} requires one value`);
46
+ values.set(key, value);
47
+ }
48
+ const client = values.get("--client");
49
+ if (client !== "pi" && client !== "opencode")
50
+ throw new Error("Choose --client pi or --client opencode (OpenCode V1)");
51
+ const url = values.get("--url"), model = values.get("--model");
52
+ if (!url || !model)
53
+ throw new Error("Provide --url <Antigravity base URL> and --model <account model slug>");
54
+ return { client, url, model, configDir: values.get("--config-dir"), apiKeyEnv: values.get("--api-key-env"), setDefault: flags.has("--set-default") };
55
+ }
56
+ function setupAntigravityClient(options, entryUrl) {
57
+ const url = new URL(options.url);
58
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash)
59
+ throw new Error("Use an HTTP(S) base URL without credentials, query or fragment");
60
+ const base = url.href.replace(/\/+$/, "").replace(/\/v1$/, "");
61
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(options.model))
62
+ throw new Error("Invalid account model slug");
63
+ if (options.apiKeyEnv && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(options.apiKeyEnv))
64
+ throw new Error("Invalid API-key environment variable name");
65
+ const entry = fileURLToPath(entryUrl);
66
+ const source = entry.endsWith(".ts") ? join(dirname(entry), "..", "examples", options.client === "pi" ? "pi-extension" : "opencode-plugin", "antigravity-retry.js") : join(dirname(entry), "antigravity-clients", `${options.client}.js`);
67
+ if (!existsSync(source))
68
+ throw new Error(`Bundled ${options.client} integration missing; rebuild or reinstall Meridian`);
69
+ const directory = options.configDir ? resolve(options.configDir) : options.client === "pi" ? resolve(process.env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent")) : dirname(findOpencodeConfigPath());
70
+ const configPath = options.client === "pi" ? join(directory, "models.json") : existsSync(join(directory, "opencode.jsonc")) && !existsSync(join(directory, "opencode.json")) ? join(directory, "opencode.jsonc") : join(directory, "opencode.json");
71
+ const integrationPath = join(directory, options.client === "pi" ? "extensions" : "plugins", "meridian-antigravity.js");
72
+ if (existsSync(integrationPath) && !readFileSync(integrationPath, "utf8").startsWith(marker))
73
+ throw new Error(`Unmanaged integration at ${integrationPath}; move it before setup`);
74
+ const integrationDirectory = dirname(integrationPath);
75
+ const signature = readFileSync(source, "utf8").split(`
76
+ `)[0];
77
+ if (existsSync(integrationDirectory)) {
78
+ for (const name of readdirSync(integrationDirectory)) {
79
+ const path = join(integrationDirectory, name);
80
+ if (path !== integrationPath && /\.[cm]?js$/.test(name) && lstatSync(path).isFile() && readFileSync(path, "utf8").includes(signature))
81
+ throw new Error(`Another Antigravity integration exists at ${path}; remove that manual copy before setup`);
82
+ }
83
+ }
84
+ const { text, config } = readConfig(configPath, options.client);
85
+ if (options.client === "opencode") {
86
+ const policies = [config];
87
+ const sibling = join(directory, configPath.endsWith(".jsonc") ? "opencode.json" : "opencode.jsonc");
88
+ if (existsSync(sibling)) {
89
+ const other = readConfig(sibling, "opencode").config;
90
+ policies.push(other);
91
+ if (record(other.provider, "Sibling providers")["meridian-agy"] !== undefined)
92
+ throw new Error(`Antigravity is also configured in ${sibling}; resolve the duplicate before setup`);
93
+ }
94
+ for (const policy of policies)
95
+ for (const field of ["enabled_providers", "disabled_providers"]) {
96
+ const value = policy[field];
97
+ if (value !== undefined && (!Array.isArray(value) || value.some((item) => typeof item !== "string")))
98
+ throw new Error(`${field} must be an array of provider IDs`);
99
+ if (Array.isArray(value) && (field === "enabled_providers" ? !value.includes("meridian-agy") : value.includes("meridian-agy")))
100
+ throw new Error(`${field} excludes meridian-agy; update that explicit provider policy before setup`);
101
+ }
102
+ }
103
+ const edits = [];
104
+ const root = options.client === "pi" ? "providers" : "provider";
105
+ const providers = record(config[root], root);
106
+ const provider = record(providers["meridian-agy"], "Antigravity provider");
107
+ const put = (path, value) => edits.push({ path, value });
108
+ if (options.client === "pi") {
109
+ if (provider.models !== undefined && !Array.isArray(provider.models))
110
+ throw new Error("Pi models must be an array");
111
+ const models = Array.isArray(provider.models) ? [...provider.models] : [];
112
+ const matches = models.flatMap((model, index2) => object(model) && model.id === options.model ? [index2] : []);
113
+ if (matches.length > 1)
114
+ throw new Error("Duplicate Pi model IDs; resolve them before setup");
115
+ const index = matches[0] ?? models.length;
116
+ models[index] = { ...record(models[index], "Pi model"), id: options.model, name: `Antigravity ${options.model}`, reasoning: false, input: ["text", "image"], contextWindow: 128000, maxTokens: 4096 };
117
+ put([root, "meridian-agy", "api"], "anthropic-messages");
118
+ put([root, "meridian-agy", "baseUrl"], base);
119
+ put([root, "meridian-agy", "apiKey"], options.apiKeyEnv ?? provider.apiKey ?? "local-placeholder");
120
+ put([root, "meridian-agy", "models"], models);
121
+ } else {
122
+ const providerOptions = record(provider.options, "Antigravity provider options");
123
+ const model = record(record(provider.models, "Antigravity models")[options.model], "Antigravity model");
124
+ put([root, "meridian-agy", "npm"], "@ai-sdk/anthropic");
125
+ put([root, "meridian-agy", "name"], "Antigravity through Meridian");
126
+ put([root, "meridian-agy", "options", "baseURL"], `${base}/v1`);
127
+ put([root, "meridian-agy", "options", "apiKey"], options.apiKeyEnv ? `{env:${options.apiKeyEnv}}` : providerOptions.apiKey ?? "local-placeholder");
128
+ put([root, "meridian-agy", "models", options.model], { ...model, name: `Antigravity ${options.model}`, limit: { ...record(model.limit, "Model limits"), context: 128000, output: 4096 }, temperature: false, reasoning: false, tool_call: true, modalities: { input: ["text", "image"], output: ["text"] } });
129
+ if (options.setDefault) {
130
+ put(["model"], `meridian-agy/${options.model}`);
131
+ put(["small_model"], `meridian-agy/${options.model}`);
132
+ }
133
+ }
134
+ let updated = text;
135
+ for (const edit of edits)
136
+ updated = applyEdits(updated, modify(updated, edit.path, edit.value, { formattingOptions: { insertSpaces: true, tabSize: 2 } }));
137
+ const writes = [{ path: integrationPath, text: marker + readFileSync(source, "utf8") }, { path: configPath, text: updated }];
138
+ if (options.client === "pi" && options.setDefault) {
139
+ const path = join(directory, "settings.json");
140
+ const settings = readConfig(path, "pi");
141
+ let value = settings.text;
142
+ for (const [key, selected] of [["defaultProvider", "meridian-agy"], ["defaultModel", options.model], ["defaultThinkingLevel", "off"]])
143
+ value = applyEdits(value, modify(value, [key], selected, { formattingOptions: { insertSpaces: true, tabSize: 2 } }));
144
+ writes.push({ path, text: value });
145
+ }
146
+ const prepared = writes.map((write) => {
147
+ if (existsSync(write.path) && !lstatSync(write.path).isFile())
148
+ throw new Error(`Refusing to replace a non-regular file: ${write.path}`);
149
+ const original = existsSync(write.path) ? readFileSync(write.path, "utf8") : undefined;
150
+ return { ...write, original, mode: original === undefined ? 384 : lstatSync(write.path).mode & 511, backup: `${write.path}.bak-${randomUUID()}` };
151
+ }).filter((write) => write.text !== write.original);
152
+ const backups = [], changed = [];
153
+ try {
154
+ for (const write of prepared) {
155
+ mkdirSync(dirname(write.path), { recursive: true });
156
+ if (write.original !== undefined) {
157
+ writeFileSync(write.backup, write.original, { flag: "wx", mode: 384 });
158
+ backups.push(write.backup);
159
+ }
160
+ }
161
+ for (const write of prepared) {
162
+ if ((existsSync(write.path) ? readFileSync(write.path, "utf8") : undefined) !== write.original)
163
+ throw new Error(`Configuration changed during setup: ${write.path}`);
164
+ const temporary = `${write.path}.tmp-${randomUUID()}`;
165
+ try {
166
+ writeFileSync(temporary, write.text, { flag: "wx", mode: write.mode });
167
+ renameSync(temporary, write.path);
168
+ } finally {
169
+ if (existsSync(temporary))
170
+ unlinkSync(temporary);
171
+ }
172
+ changed.push(write.path);
173
+ }
174
+ } catch (error) {
175
+ for (const path of changed.reverse()) {
176
+ const write = prepared.find((item) => item.path === path);
177
+ if (write.original === undefined)
178
+ unlinkSync(path);
179
+ else {
180
+ chmodSync(write.backup, write.mode);
181
+ renameSync(write.backup, path);
182
+ }
183
+ }
184
+ throw error;
185
+ }
186
+ return { client: options.client, configPath, integrationPath, changed, backups, baseUrl: base, model: options.model };
187
+ }
188
+ export {
189
+ setupAntigravityClient,
190
+ parseAntigravitySetupArgs
191
+ };
@@ -0,0 +1,30 @@
1
+ import {
2
+ __esm
3
+ } from "./cli-p9swy5t3.js";
4
+
5
+ // src/env.ts
6
+ function env(suffix) {
7
+ return process.env[`MERIDIAN_${suffix}`] ?? process.env[`CLAUDE_PROXY_${suffix}`];
8
+ }
9
+ function envBool(suffix) {
10
+ const val = env(suffix);
11
+ return val === "1" || val === "true" || val === "yes";
12
+ }
13
+ function resolvePassthrough(defaultValue) {
14
+ const val = env("PASSTHROUGH");
15
+ if (val === "1" || val === "true" || val === "yes")
16
+ return true;
17
+ if (val === "0" || val === "false" || val === "no")
18
+ return false;
19
+ return defaultValue;
20
+ }
21
+ function envInt(suffix, defaultValue) {
22
+ const val = env(suffix);
23
+ if (!val)
24
+ return defaultValue;
25
+ const parsed = parseInt(val, 10);
26
+ return Number.isFinite(parsed) ? parsed : defaultValue;
27
+ }
28
+ var init_env = () => {};
29
+
30
+ export { env, envBool, resolvePassthrough, envInt, init_env };