okengine 0.13.0 → 0.15.2
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/manifest.v1.schema.json +7 -1
- package/package.json +4 -5
- package/site/content/docs/ai/mcp.mdx +27 -2
- package/site/content/docs/elements/ai.mdx +59 -11
- package/site/content/docs/elements/channel.mdx +1 -0
- package/site/content/docs/elements/clock.mdx +30 -12
- package/site/content/docs/elements/flow.mdx +9 -3
- package/site/content/docs/elements/gate.mdx +25 -22
- package/site/content/docs/elements/signal.mdx +71 -25
- package/site/content/docs/elements/store.mdx +87 -28
- package/site/content/docs/elements/vault.mdx +1 -0
- package/site/content/docs/get-started/basic-usage.mdx +22 -13
- package/site/content/docs/get-started/index.mdx +5 -0
- package/site/content/docs/get-started/installation.mdx +2 -2
- package/site/content/docs/get-started/introduction.mdx +11 -3
- package/site/content/docs/get-started/meta.json +1 -1
- package/site/content/docs/get-started/project-structure.mdx +932 -0
- package/site/content/docs/reference/cli.md +13 -10
- package/site/content/docs/reference/client.mdx +25 -21
- package/site/content/docs/reference/fx.mdx +27 -22
- package/site/content/docs/reference/security.md +1 -1
- package/src/auth/bindings.ts +14 -5
- package/src/auth/cross-plane.ts +10 -3
- package/src/cli/ai-setup/ai-setup.test.ts +62 -0
- package/src/cli/ai-setup/apply.ts +32 -1
- package/src/cli/ai-setup/recommend.test.ts +25 -0
- package/src/cli/ai-setup/recommend.ts +8 -3
- package/src/cli/attach-host-console.test.ts +3 -2
- package/src/cli/attach-host-console.ts +3 -0
- package/src/cli/build.test.ts +4 -2
- package/src/cli/db-seed.ts +48 -15
- package/src/cli/db.test.ts +104 -0
- package/src/cli/db.ts +122 -10
- package/src/cli/dev-controls.test.ts +20 -0
- package/src/cli/dev-controls.ts +13 -0
- package/src/cli/dev.test.ts +15 -4
- package/src/cli/dev.ts +102 -29
- package/src/cli/ensure-drizzle-config.ts +2 -1
- package/src/cli/tui/DevLive.tsx +12 -2
- package/src/cli/tui/keys.test.ts +2 -1
- package/src/cli/tui/keys.ts +3 -2
- package/src/compiler/aot.test.ts +1 -1
- package/src/compiler/effects-infer.ts +58 -3
- package/src/compiler/extract.test.ts +458 -29
- package/src/compiler/extract.ts +387 -69
- package/src/compiler/flow-path.test.ts +96 -0
- package/src/compiler/flow-path.ts +173 -0
- package/src/compiler/generate-adopt.test.ts +112 -3
- package/src/compiler/generate-adopt.ts +280 -26
- package/src/compiler/response.ts +45 -1
- package/src/compiler/schema-from-ast.ts +609 -0
- package/src/console/server/ai.ts +5 -2
- package/src/console/server/bind.ts +2 -2
- package/src/console/server/console.test.ts +3 -0
- package/src/console/server/flows.ts +121 -40
- package/src/console/server/invoke-user-flow.test.ts +3 -1
- package/src/console/server/invoke-user-flow.ts +17 -2
- package/src/console/server/iso-at.test.ts +9 -0
- package/src/console/server/iso-at.ts +11 -0
- package/src/console/server/security-headers.ts +6 -3
- package/src/console/server/serve.ts +17 -2
- package/src/console/server/spa-proxy.test.ts +1 -0
- package/src/console/server/sql-catalog.ts +147 -7
- package/src/console/server/sql-rls.test.ts +16 -0
- package/src/console/server/state.ts +8 -0
- package/src/console/server/store.test.ts +7 -4
- package/src/console/server/store.ts +142 -58
- package/src/console/ui-next/dist/assets/agent-disclosure-C0X1fbWF.js +1 -0
- package/src/console/ui-next/dist/assets/cache-glyph-F1FI122b.js +1 -0
- package/src/console/ui-next/dist/assets/{call-pii-button-bqkxMrJH.js → call-pii-button-DEDCl_j3.js} +1 -1
- package/src/console/ui-next/dist/assets/collapsible-LPqGvfoz.js +1 -0
- package/src/console/ui-next/dist/assets/{confirm-sheet-DDCRmG62.js → confirm-sheet-3ptDzXbz.js} +1 -1
- package/src/console/ui-next/dist/assets/{duration-tone-oiRxPVsZ.js → duration-tone-sC3lGABz.js} +1 -1
- package/src/console/ui-next/dist/assets/{explorer-empty-HjTnVQoR.js → explorer-empty-BvYrygyO.js} +1 -1
- package/src/console/ui-next/dist/assets/flows-page-Dluo1Bd7.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CvDPvveV.js → highlighted-json-CS_O8L-r.js} +1 -1
- package/src/console/ui-next/dist/assets/http-method--sWDdXSB.js +1 -0
- package/src/console/ui-next/dist/assets/index-Ca3HZMVq.js +66 -0
- package/src/console/ui-next/dist/assets/index-UtTDRQpS.css +2 -0
- package/src/console/ui-next/dist/assets/{link-DF7SZ9Ek.js → link-COeyggt-.js} +1 -1
- package/src/console/ui-next/dist/assets/observability-page-HK9-BO8z.js +4 -0
- package/src/console/ui-next/dist/assets/react-D8E3mtu1.js +1 -0
- package/src/console/ui-next/dist/assets/replica-lag-DBIFFf7d.js +18 -0
- package/src/console/ui-next/dist/assets/sheet-form-Dnwa5oOR.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-CoqwEIi0.js +1 -0
- package/src/console/ui-next/dist/assets/skeleton-BHcfDYcb.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-BSohH3wM.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CFIGRnnA.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-BgLl34w7.js +54 -0
- package/src/console/ui-next/dist/assets/units-page-B0cFE76A.js +1 -0
- package/src/console/ui-next/dist/assets/{use-vault-list-Cl79j_ku.js → use-vault-list-BLbZhzyF.js} +1 -1
- package/src/console/ui-next/dist/assets/vault-page-B1SbYe10.js +2 -0
- package/src/console/ui-next/dist/index.html +6 -6
- package/src/console/ui-next/seed-invoke-host.ts +2 -0
- package/src/console/ui-next/src/client.ts +35 -7
- package/src/console/ui-next/src/components/motion/table/index.tsx +16 -0
- package/src/console/ui-next/src/components/motion/table/types.ts +4 -0
- package/src/console/ui-next/src/components/ui/kbd.tsx +2 -2
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.test.ts +18 -0
- package/src/console/ui-next/src/features/flows/graph/build-flow-graph.ts +14 -1
- package/src/console/ui-next/src/features/flows/graph/neighborhood.test.ts +17 -0
- package/src/console/ui-next/src/features/flows/graph/neighborhood.ts +16 -3
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +3 -1
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-method.ts +19 -0
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +9 -3
- package/src/console/ui-next/src/features/flows/traces/trace-detail.test.ts +29 -2
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +10 -3
- package/src/console/ui-next/src/features/observability/lib/ask-count.test.ts +25 -0
- package/src/console/ui-next/src/features/observability/lib/ask-count.ts +4 -1
- package/src/console/ui-next/src/features/store/detail/browse-section.tsx +45 -1
- package/src/console/ui-next/src/features/store/detail/resource-panel.tsx +78 -16
- package/src/console/ui-next/src/features/store/detail/store-row-detail-sheet.tsx +155 -14
- package/src/console/ui-next/src/features/store/explorer/store-tree.tsx +33 -133
- package/src/console/ui-next/src/features/store/files/file-preview.tsx +4 -1
- package/src/console/ui-next/src/features/store/grid/rls-policy-sheet.tsx +326 -165
- package/src/console/ui-next/src/features/store/grid/sql-insert-sheet.tsx +6 -0
- package/src/console/ui-next/src/features/store/grid/store-data-grid.tsx +50 -3
- package/src/console/ui-next/src/features/store/lib/files-meta.test.ts +3 -0
- package/src/console/ui-next/src/features/store/lib/files-meta.ts +12 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.test.ts +25 -0
- package/src/console/ui-next/src/features/store/lib/query-defaults.ts +85 -3
- package/src/console/ui-next/src/features/store/lib/query-gate.ts +30 -4
- package/src/console/ui-next/src/features/store/lib/rls-policy.test.ts +139 -0
- package/src/console/ui-next/src/features/store/lib/rls-policy.ts +223 -21
- package/src/console/ui-next/src/features/store/lib/schema-graph.ts +4 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.test.ts +24 -0
- package/src/console/ui-next/src/features/store/lib/sql-catalog.ts +19 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.test.ts +6 -0
- package/src/console/ui-next/src/features/store/lib/store-tree.ts +13 -0
- package/src/console/ui-next/src/features/store/query/query-console.tsx +48 -14
- package/src/console/ui-next/src/features/store/query/query-gate-menu.tsx +19 -20
- package/src/console/ui-next/src/features/store/query/query-gate-parts.tsx +2 -1
- package/src/console/ui-next/src/features/store/query/query-results.tsx +110 -83
- package/src/console/ui-next/src/features/store/schema/schema-table-node.tsx +17 -0
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +112 -72
- package/src/console/ui-next/src/features/units/detail/effects-summary.tsx +12 -4
- package/src/console/ui-next/src/features/units/lib/contract-input.test.ts +206 -1
- package/src/console/ui-next/src/features/units/lib/contract-input.ts +55 -0
- package/src/console/ui-next/src/features/vault/lib/types.ts +1 -1
- package/src/docker/compose-health.test.ts +27 -0
- package/src/docker/compose-health.ts +19 -3
- package/src/docker/docker.test.ts +4 -1
- package/src/docker/dockerfile.ts +1 -1
- package/src/docker/recipes/pgdog.ts +2 -0
- package/src/drivers/ai-anthropic.ts +5 -0
- package/src/drivers/ai-ollama.ts +49 -30
- package/src/drivers/ai-openai-compatible.ts +57 -46
- package/src/drivers/ai-providers.test.ts +3 -0
- package/src/drivers/bun-native-completeness.test.ts +7 -9
- package/src/drivers/clock-postgres.ts +13 -9
- package/src/drivers/instances-postgres.ts +4 -6
- package/src/drivers/journal-postgres.ts +13 -9
- package/src/drivers/pg-rls.test.ts +87 -0
- package/src/drivers/pg-rls.ts +233 -7
- package/src/drivers/pglite.ts +26 -3
- package/src/drivers/postgres.test.ts +23 -0
- package/src/drivers/postgres.ts +133 -9
- package/src/drivers/redis.ts +11 -4
- package/src/drivers/signal-redis.ts +24 -14
- package/src/drivers/signal-types.ts +2 -1
- package/src/drivers/types.ts +10 -0
- package/src/elements/ai/declare.ts +109 -0
- package/src/elements/ai/errors.test.ts +5 -1
- package/src/elements/ai/errors.ts +30 -2
- package/src/elements/ai/eval.ts +4 -6
- package/src/elements/ai/mcp-client.test.ts +206 -0
- package/src/elements/ai/mcp-client.ts +362 -0
- package/src/elements/ai/mcp-http.ts +159 -0
- package/src/elements/ai/mcp-mock.ts +134 -0
- package/src/elements/ai/mcp-protocol.ts +234 -0
- package/src/elements/ai/mcp-stdio.test.ts +50 -0
- package/src/elements/ai/mcp-stdio.ts +212 -0
- package/src/elements/ai/mcp-transport.ts +70 -0
- package/src/elements/ai/runtime.ts +159 -29
- package/src/elements/ai.test.ts +139 -0
- package/src/elements/ai.ts +16 -0
- package/src/elements/clock/declare.ts +26 -1
- package/src/elements/clock/health.test.ts +43 -0
- package/src/elements/clock/runtime.ts +55 -2
- package/src/elements/clock/schedule.ts +71 -142
- package/src/elements/clock.test.ts +9 -40
- package/src/elements/clock.ts +6 -1
- package/src/elements/gate/boot.ts +5 -5
- package/src/elements/gate/declare.ts +36 -4
- package/src/elements/gate/flatten.ts +1 -1
- package/src/elements/gate/runtime.ts +1 -1
- package/src/elements/index.ts +2 -0
- package/src/elements/signal/declare.ts +2 -1
- package/src/elements/signal/runtime.ts +16 -1
- package/src/elements/signal.ts +1 -0
- package/src/elements/store/cache.test.ts +2 -0
- package/src/elements/store/cache.ts +3 -3
- package/src/elements/store/declare.ts +9 -6
- package/src/elements/store/emit-drizzle.ts +44 -8
- package/src/elements/store/resource.test.ts +1 -1
- package/src/elements/store/rls-identity.test.ts +95 -0
- package/src/elements/store/rls-identity.ts +116 -0
- package/src/elements/store/runtime.ts +4 -0
- package/src/elements/store/schema-decl.test.ts +37 -0
- package/src/elements/store/schema-decl.ts +161 -0
- package/src/elements/store/seed.ts +1 -1
- package/src/elements/store/sql-condition.test.ts +12 -0
- package/src/elements/store/sql-condition.ts +26 -2
- package/src/elements/store/sql-rls-isolation.test.ts +165 -0
- package/src/elements/store/sql-rls-stamp.test.ts +117 -0
- package/src/elements/store/sql-session.ts +81 -3
- package/src/elements/store/upsert-app.test.ts +1 -1
- package/src/elements/store.ts +14 -0
- package/src/elements/vault/builtin-adapter.ts +17 -0
- package/src/full.ts +2 -0
- package/src/http.ts +20 -3
- package/src/index.ts +14 -1
- package/src/kernel/adopt-barrel-fresh.test.ts +1 -1
- package/src/kernel/adopt-routes.ts +30 -2
- package/src/kernel/app.ts +227 -77
- package/src/kernel/auto-cache.test.ts +8 -8
- package/src/kernel/auto-registry.test.ts +54 -9
- package/src/kernel/boot-bind/ai.ts +24 -0
- package/src/kernel/boot-bind/clock.ts +2 -0
- package/src/kernel/boot.test.ts +4 -4
- package/src/kernel/boot.ts +5 -3
- package/src/kernel/effects-stamping.test.ts +3 -3
- package/src/kernel/element-registries.ts +15 -5
- package/src/kernel/errors.ts +24 -0
- package/src/kernel/flow-units.ts +64 -0
- package/src/kernel/flow.ts +29 -17
- package/src/kernel/fx-dead-letters.test.ts +77 -0
- package/src/kernel/fx.test.ts +22 -0
- package/src/kernel/fx.ts +135 -6
- package/src/kernel/horizontal-child.ts +4 -4
- package/src/kernel/http-path-pending.ts +20 -0
- package/src/kernel/http-resource.ts +4 -0
- package/src/kernel/http-stream.test.ts +174 -0
- package/src/kernel/index.ts +2 -0
- package/src/kernel/pipeline.test.ts +2 -2
- package/src/kernel/plugin-elements.test.ts +1 -1
- package/src/kernel/plugin-needs.test.ts +1 -1
- package/src/kernel/ready.test.ts +2 -2
- package/src/kernel/resource-mount.test.ts +14 -0
- package/src/kernel/run-duration.test.ts +2 -2
- package/src/kernel/stamp-http.test.ts +79 -0
- package/src/kernel/stamp-http.ts +75 -0
- package/src/kernel/triggers.ts +52 -27
- package/src/manifest/mcp-ref.ts +88 -0
- package/src/manifest/types.ts +41 -2
- package/src/manifest/validate.test.ts +20 -0
- package/src/mcp/docs-server.ts +1 -1
- package/src/mcp/server.ts +1 -1
- package/src/plugins/compression.test.ts +21 -0
- package/src/plugins/compression.ts +1 -0
- package/src/release/measure.ts +2 -2
- package/src/runtime/bun.ts +41 -4
- package/src/runtime/json-code-block.test.ts +2 -2
- package/src/runtime/serve.test.ts +4 -4
- package/src/term.test.ts +52 -4
- package/src/term.ts +125 -17
- package/src/test/reset-element-registries.ts +10 -3
- package/src/cli/ask-seed.test.ts +0 -96
- package/src/cli/ask-seed.ts +0 -82
- package/src/console/ui-next/dist/assets/cache-glyph-CLPBqZeb.js +0 -1
- package/src/console/ui-next/dist/assets/collapsible-DYb0xU8C.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-Dg8CTE29.js +0 -1
- package/src/console/ui-next/dist/assets/http-method-BJ92Z_ke.js +0 -1
- package/src/console/ui-next/dist/assets/index-Bp-R7jtM.js +0 -66
- package/src/console/ui-next/dist/assets/index-D4Ldtj79.css +0 -2
- package/src/console/ui-next/dist/assets/observability-page-DAnpEaq1.js +0 -4
- package/src/console/ui-next/dist/assets/query-gate-parts-1m8m1iNp.js +0 -2
- package/src/console/ui-next/dist/assets/react-B1ML8gxg.js +0 -1
- package/src/console/ui-next/dist/assets/replica-lag-C8_BRt2x.js +0 -18
- package/src/console/ui-next/dist/assets/sheet-form-D-ata7jy.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-DO4IsVqv.js +0 -1
- package/src/console/ui-next/dist/assets/skeleton-CL_X0GCj.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-v3LXdYpr.js +0 -45
- package/src/console/ui-next/dist/assets/trace-detail-sheet-D16lWQMt.js +0 -2
- package/src/console/ui-next/dist/assets/units-page-BtQ0bqMe.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-Ca-MvcmJ.js +0 -2
|
@@ -6,6 +6,7 @@ import { HugeiconsIcon } from "@hugeicons/react";
|
|
|
6
6
|
import type { JSX } from "react";
|
|
7
7
|
import type { Effects } from "../../../../../../manifest/types.ts";
|
|
8
8
|
import { SectionHead } from "@/components/explorer/section-head.tsx";
|
|
9
|
+
import { mcpCallChipLabel } from "@/features/flows/traces/effect-summary.ts";
|
|
9
10
|
import {
|
|
10
11
|
effectBarColor,
|
|
11
12
|
effectKindIcon,
|
|
@@ -38,17 +39,24 @@ export function EffectsSummary({ effects }: EffectsSummaryProps): JSX.Element |
|
|
|
38
39
|
<ul className="flex flex-wrap gap-1.5">
|
|
39
40
|
{chips.map((c) => {
|
|
40
41
|
const color = effectBarColor(c.kind);
|
|
41
|
-
const Icon = effectKindIcon(c.kind);
|
|
42
|
+
const Icon = effectKindIcon(c.kind, c.ref);
|
|
43
|
+
const mcpLabel = c.kind === "call" ? mcpCallChipLabel(c.ref) : null;
|
|
42
44
|
return (
|
|
43
45
|
<li
|
|
44
46
|
key={`${c.kind}:${c.ref}`}
|
|
45
47
|
className="inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 font-mono text-[10px]"
|
|
46
48
|
style={{ borderColor: `${color}55`, color }}
|
|
47
|
-
title={`${EFFECT_KIND_LABEL[c.kind]} ${c.ref}`}
|
|
49
|
+
title={mcpLabel ?? `${EFFECT_KIND_LABEL[c.kind]} ${c.ref}`}
|
|
48
50
|
>
|
|
49
51
|
<HugeiconsIcon icon={Icon} className="size-3" />
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
{mcpLabel ? (
|
|
53
|
+
<span className="text-foreground/90">{mcpLabel}</span>
|
|
54
|
+
) : (
|
|
55
|
+
<>
|
|
56
|
+
<span className="text-muted-foreground">{EFFECT_KIND_LABEL[c.kind]}</span>
|
|
57
|
+
<span className="text-foreground/90">{c.ref}</span>
|
|
58
|
+
</>
|
|
59
|
+
)}
|
|
52
60
|
</li>
|
|
53
61
|
);
|
|
54
62
|
})}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { describe, expect, test } from "bun:test";
|
|
6
|
-
import { splitContractInput } from "./contract-input.ts";
|
|
6
|
+
import { pickSeedFields, splitCallApiInput, splitContractInput } from "./contract-input.ts";
|
|
7
7
|
import { fieldsFromSchema } from "./fields-from-schema.ts";
|
|
8
8
|
|
|
9
9
|
const LIST_IN = fieldsFromSchema({
|
|
@@ -45,3 +45,208 @@ describe("splitContractInput", () => {
|
|
|
45
45
|
expect(split.fields.map((f) => f.name)).toEqual(["title"]);
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
|
+
|
|
49
|
+
describe("splitCallApiInput", () => {
|
|
50
|
+
const idOnly = fieldsFromSchema({
|
|
51
|
+
type: "object",
|
|
52
|
+
required: ["id"],
|
|
53
|
+
properties: { id: { type: "string", minLength: 1 } },
|
|
54
|
+
});
|
|
55
|
+
const list = LIST_IN;
|
|
56
|
+
const nestedList = fieldsFromSchema({
|
|
57
|
+
type: "object",
|
|
58
|
+
required: ["id"],
|
|
59
|
+
properties: {
|
|
60
|
+
id: { type: "string", minLength: 1 },
|
|
61
|
+
q: { type: "string" },
|
|
62
|
+
limit: { type: "integer" },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
const create = fieldsFromSchema({
|
|
66
|
+
type: "object",
|
|
67
|
+
required: ["title"],
|
|
68
|
+
properties: { title: { type: "string" }, spaceKey: { type: "string" } },
|
|
69
|
+
});
|
|
70
|
+
const assign = fieldsFromSchema({
|
|
71
|
+
type: "object",
|
|
72
|
+
required: ["id", "assigneeEmail"],
|
|
73
|
+
properties: { id: { type: "string" }, assigneeEmail: { type: "string" } },
|
|
74
|
+
});
|
|
75
|
+
const patch = fieldsFromSchema({
|
|
76
|
+
type: "object",
|
|
77
|
+
required: ["id"],
|
|
78
|
+
properties: {
|
|
79
|
+
id: { type: "string" },
|
|
80
|
+
title: { type: "string" },
|
|
81
|
+
status: { type: "string" },
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
const putDraft = fieldsFromSchema({
|
|
85
|
+
type: "object",
|
|
86
|
+
required: ["id", "title"],
|
|
87
|
+
properties: { id: { type: "string" }, title: { type: "string" }, body: { type: "string" } },
|
|
88
|
+
});
|
|
89
|
+
const search = fieldsFromSchema({
|
|
90
|
+
type: "object",
|
|
91
|
+
required: ["q"],
|
|
92
|
+
properties: { q: { type: "string" }, limit: { type: "integer" } },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const cases: readonly {
|
|
96
|
+
readonly name: string;
|
|
97
|
+
readonly method: string;
|
|
98
|
+
readonly pathParams: readonly string[];
|
|
99
|
+
readonly fields: ReturnType<typeof fieldsFromSchema>;
|
|
100
|
+
readonly path: readonly string[];
|
|
101
|
+
readonly query: readonly string[];
|
|
102
|
+
readonly body: readonly string[];
|
|
103
|
+
}[] = [
|
|
104
|
+
{
|
|
105
|
+
name: "GET tasks.get",
|
|
106
|
+
method: "GET",
|
|
107
|
+
pathParams: ["id"],
|
|
108
|
+
fields: idOnly,
|
|
109
|
+
path: ["id"],
|
|
110
|
+
query: [],
|
|
111
|
+
body: [],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "GET tasks.list",
|
|
115
|
+
method: "GET",
|
|
116
|
+
pathParams: [],
|
|
117
|
+
fields: list,
|
|
118
|
+
path: [],
|
|
119
|
+
query: ["q", "teamKey", "limit", "offset", "cursor", "orderBy", "order"],
|
|
120
|
+
body: [],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "GET comments.list /tasks/:id/comments",
|
|
124
|
+
method: "GET",
|
|
125
|
+
pathParams: ["id"],
|
|
126
|
+
fields: nestedList,
|
|
127
|
+
path: ["id"],
|
|
128
|
+
query: ["q", "limit"],
|
|
129
|
+
body: [],
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "HEAD main.health",
|
|
133
|
+
method: "HEAD",
|
|
134
|
+
pathParams: [],
|
|
135
|
+
fields: [],
|
|
136
|
+
path: [],
|
|
137
|
+
query: [],
|
|
138
|
+
body: [],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "DELETE tasks.delete",
|
|
142
|
+
method: "DELETE",
|
|
143
|
+
pathParams: ["id"],
|
|
144
|
+
fields: idOnly,
|
|
145
|
+
path: ["id"],
|
|
146
|
+
query: [],
|
|
147
|
+
body: [],
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "POST tasks.create",
|
|
151
|
+
method: "POST",
|
|
152
|
+
pathParams: [],
|
|
153
|
+
fields: create,
|
|
154
|
+
path: [],
|
|
155
|
+
query: [],
|
|
156
|
+
body: ["title", "spaceKey"],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "POST tasks.assign /tasks/:id/assign",
|
|
160
|
+
method: "POST",
|
|
161
|
+
pathParams: ["id"],
|
|
162
|
+
fields: assign,
|
|
163
|
+
path: ["id"],
|
|
164
|
+
query: [],
|
|
165
|
+
body: ["assigneeEmail"],
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "POST tasks.archive path-only",
|
|
169
|
+
method: "POST",
|
|
170
|
+
pathParams: ["id"],
|
|
171
|
+
fields: idOnly,
|
|
172
|
+
path: ["id"],
|
|
173
|
+
query: [],
|
|
174
|
+
body: [],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "PATCH tasks.update",
|
|
178
|
+
method: "PATCH",
|
|
179
|
+
pathParams: ["id"],
|
|
180
|
+
fields: patch,
|
|
181
|
+
path: ["id"],
|
|
182
|
+
query: [],
|
|
183
|
+
body: ["title", "status"],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "PUT drafts.save",
|
|
187
|
+
method: "PUT",
|
|
188
|
+
pathParams: ["id"],
|
|
189
|
+
fields: putDraft,
|
|
190
|
+
path: ["id"],
|
|
191
|
+
query: [],
|
|
192
|
+
body: ["title", "body"],
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "QUERY search.query",
|
|
196
|
+
method: "QUERY",
|
|
197
|
+
pathParams: [],
|
|
198
|
+
fields: search,
|
|
199
|
+
path: [],
|
|
200
|
+
query: [],
|
|
201
|
+
body: ["q", "limit"],
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "OPTIONS leftover stays body",
|
|
205
|
+
method: "OPTIONS",
|
|
206
|
+
pathParams: [],
|
|
207
|
+
fields: search,
|
|
208
|
+
path: [],
|
|
209
|
+
query: [],
|
|
210
|
+
body: ["q", "limit"],
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
for (const row of cases) {
|
|
215
|
+
test(row.name, () => {
|
|
216
|
+
const split = splitCallApiInput(row.fields, {
|
|
217
|
+
http: true,
|
|
218
|
+
method: row.method,
|
|
219
|
+
pathParams: row.pathParams,
|
|
220
|
+
});
|
|
221
|
+
expect(split.path).toEqual([...row.path]);
|
|
222
|
+
expect(split.query.map((f) => f.name)).toEqual([...row.query]);
|
|
223
|
+
expect(split.body.map((f) => f.name)).toEqual([...row.body]);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
test("non-HTTP keeps the full input as body", () => {
|
|
228
|
+
const split = splitCallApiInput(idOnly);
|
|
229
|
+
expect(split.path).toEqual([]);
|
|
230
|
+
expect(split.query).toEqual([]);
|
|
231
|
+
expect(split.body.map((f) => f.name)).toEqual(["id"]);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("Signal / CDC / Call-only never grow Path params from an id field", () => {
|
|
235
|
+
for (const kind of ["signal", "cdc", "internal"] as const) {
|
|
236
|
+
const split = splitCallApiInput(idOnly, { http: false, method: kind });
|
|
237
|
+
expect(split.path).toEqual([]);
|
|
238
|
+
expect(split.query).toEqual([]);
|
|
239
|
+
expect(split.body.map((f) => f.name)).toEqual(["id"]);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("pickSeedFields", () => {
|
|
245
|
+
test("keeps only named section keys", () => {
|
|
246
|
+
const title = fieldsFromSchema({
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: { title: { type: "string" } },
|
|
249
|
+
});
|
|
250
|
+
expect(pickSeedFields({ id: "x", title: "n" }, title)).toEqual({ title: "n" });
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Split flow input into query/path parameters vs body / filter fields.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { httpMethodHasBody } from "@/features/flows/traces/http-method.ts";
|
|
5
6
|
import type { FormField } from "./fields-from-schema.ts";
|
|
6
7
|
|
|
7
8
|
/** List / page / sort keys — query parameters, not resource fields. */
|
|
@@ -66,3 +67,57 @@ function pathParamField(name: string): FormField {
|
|
|
66
67
|
required: true,
|
|
67
68
|
};
|
|
68
69
|
}
|
|
70
|
+
|
|
71
|
+
/** Call API request sections — path vs query vs JSON body. */
|
|
72
|
+
export type CallApiInputSplit = {
|
|
73
|
+
readonly path: readonly string[];
|
|
74
|
+
readonly query: readonly FormField[];
|
|
75
|
+
readonly body: readonly FormField[];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Partition `flow.in` the way the typed client puts fields on the wire.
|
|
80
|
+
*
|
|
81
|
+
* Path tokens never appear in query or body. `GET` / `HEAD` / `DELETE`
|
|
82
|
+
* leftover fields are query. Other HTTP verbs and non-HTTP triggers keep a
|
|
83
|
+
* JSON body (minus path tokens).
|
|
84
|
+
*
|
|
85
|
+
* @param input - `flow.in` fields
|
|
86
|
+
* @param options - Route tokens + HTTP method
|
|
87
|
+
*/
|
|
88
|
+
export function splitCallApiInput(
|
|
89
|
+
input: readonly FormField[],
|
|
90
|
+
options: {
|
|
91
|
+
readonly pathParams?: readonly string[];
|
|
92
|
+
readonly method?: string | null;
|
|
93
|
+
readonly http?: boolean;
|
|
94
|
+
} = {},
|
|
95
|
+
): CallApiInputSplit {
|
|
96
|
+
const pathParams = options.pathParams ?? [];
|
|
97
|
+
const pathSet = new Set(pathParams);
|
|
98
|
+
if (options.http !== true) {
|
|
99
|
+
return { path: [], query: [], body: input };
|
|
100
|
+
}
|
|
101
|
+
const leftover = input.filter((field) => !pathSet.has(field.name));
|
|
102
|
+
if (!httpMethodHasBody(options.method)) {
|
|
103
|
+
return { path: pathParams, query: leftover, body: [] };
|
|
104
|
+
}
|
|
105
|
+
return { path: pathParams, query: [], body: leftover };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Keep schema seed keys that belong to a Call API section.
|
|
110
|
+
*
|
|
111
|
+
* @param seed - Full `flow.in` seed
|
|
112
|
+
* @param fields - Section fields
|
|
113
|
+
*/
|
|
114
|
+
export function pickSeedFields(
|
|
115
|
+
seed: Record<string, unknown>,
|
|
116
|
+
fields: readonly FormField[],
|
|
117
|
+
): Record<string, unknown> {
|
|
118
|
+
const out: Record<string, unknown> = {};
|
|
119
|
+
for (const field of fields) {
|
|
120
|
+
if (Object.hasOwn(seed, field.name)) out[field.name] = seed[field.name];
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
@@ -16,6 +16,12 @@ describe("compose-health", () => {
|
|
|
16
16
|
expect(composeRowToStatus({ State: "stopped" })).toBe("error");
|
|
17
17
|
expect(composeRowToStatus({ Status: "Exited (0) 3 seconds ago" })).toBe("error");
|
|
18
18
|
expect(composeRowToStatus({ State: "running" })).toBe("ready");
|
|
19
|
+
expect(composeRowToStatus({ State: "restarting" })).toBe("pending");
|
|
20
|
+
expect(composeRowToStatus({ State: "running", Status: "Up Less than a second" })).toBe(
|
|
21
|
+
"pending",
|
|
22
|
+
);
|
|
23
|
+
expect(composeRowToStatus({ State: "running", Status: "Up 2 seconds" })).toBe("pending");
|
|
24
|
+
expect(composeRowToStatus({ State: "running", Status: "Up 2 minutes" })).toBe("ready");
|
|
19
25
|
expect(composeRowToStatus({ Status: "Up 2 seconds (health: starting)" })).toBe("pending");
|
|
20
26
|
});
|
|
21
27
|
|
|
@@ -60,6 +66,27 @@ describe("compose-health", () => {
|
|
|
60
66
|
expect(updates).toEqual(["store-sql:pending", "store-sql:ready"]);
|
|
61
67
|
});
|
|
62
68
|
|
|
69
|
+
test("watchComposeHealth waits through compose restarting", async () => {
|
|
70
|
+
let calls = 0;
|
|
71
|
+
const map = await watchComposeHealth({
|
|
72
|
+
files: ["compose.yml"],
|
|
73
|
+
cwd: "/tmp",
|
|
74
|
+
env: {},
|
|
75
|
+
intervalMs: 1,
|
|
76
|
+
timeoutMs: 5_000,
|
|
77
|
+
sleep: async () => {},
|
|
78
|
+
run: async () => {
|
|
79
|
+
calls += 1;
|
|
80
|
+
if (calls === 1) {
|
|
81
|
+
return JSON.stringify([{ Service: "store-sql", State: "restarting" }]);
|
|
82
|
+
}
|
|
83
|
+
return JSON.stringify([{ Service: "store-sql", Health: "healthy", State: "running" }]);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
expect(calls).toBe(2);
|
|
87
|
+
expect(map.get("store-sql")).toBe("ready");
|
|
88
|
+
});
|
|
89
|
+
|
|
63
90
|
test("readComposeHealth / watch use ps -a", async () => {
|
|
64
91
|
let seen: readonly string[] = [];
|
|
65
92
|
await watchComposeHealth({
|
|
@@ -20,22 +20,27 @@ export type ComposePsRow = {
|
|
|
20
20
|
export function composeRowToStatus(row: ComposePsRow): DevStatus {
|
|
21
21
|
const health = (row.Health ?? "").trim().toLowerCase();
|
|
22
22
|
const state = (row.State ?? "").trim().toLowerCase();
|
|
23
|
+
const statusText = (row.Status ?? "").toLowerCase();
|
|
23
24
|
if (health === "healthy") return "ready";
|
|
24
25
|
if (health === "unhealthy") return "error";
|
|
25
26
|
if (health === "starting") return "pending";
|
|
26
|
-
if (state === "
|
|
27
|
+
if (state === "restarting") return "pending";
|
|
28
|
+
if (state === "running" || state === "up") {
|
|
29
|
+
// Right after `compose restart`, State is "running" before Health is
|
|
30
|
+
// "starting". Treat a fresh Up without (healthy) as still coming up.
|
|
31
|
+
if (isFreshComposeUp(statusText) && !statusText.includes("(healthy)")) return "pending";
|
|
32
|
+
return "ready";
|
|
33
|
+
}
|
|
27
34
|
if (
|
|
28
35
|
state === "exited" ||
|
|
29
36
|
state === "dead" ||
|
|
30
37
|
state === "stopped" ||
|
|
31
|
-
state === "restarting" ||
|
|
32
38
|
state === "removing" ||
|
|
33
39
|
state === "oomkilled"
|
|
34
40
|
) {
|
|
35
41
|
return "error";
|
|
36
42
|
}
|
|
37
43
|
// Status text for stopped containers (`Exited (0) 2 seconds ago`).
|
|
38
|
-
const statusText = (row.Status ?? "").toLowerCase();
|
|
39
44
|
if (statusText.startsWith("exited") || statusText.startsWith("dead")) return "error";
|
|
40
45
|
if (state === "created" || state === "paused" || state.length === 0) return "pending";
|
|
41
46
|
// Status text like "Up 3 seconds (healthy)"
|
|
@@ -48,6 +53,17 @@ export function composeRowToStatus(row: ComposePsRow): DevStatus {
|
|
|
48
53
|
return "pending";
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
/**
|
|
57
|
+
* True when compose `Status` is a just-started container (health not reported yet).
|
|
58
|
+
*
|
|
59
|
+
* @param statusText - Lowercased `Status` from `docker compose ps`
|
|
60
|
+
*/
|
|
61
|
+
function isFreshComposeUp(statusText: string): boolean {
|
|
62
|
+
if (statusText.includes("less than a second")) return true;
|
|
63
|
+
const seconds = /up\s+(\d+)\s+seconds?/i.exec(statusText);
|
|
64
|
+
return seconds !== null && Number(seconds[1]) < 8;
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
/**
|
|
52
68
|
* Parse NDJSON or a JSON array from `docker compose ps --format json`.
|
|
53
69
|
*
|
|
@@ -392,11 +392,13 @@ describe("image recipes", () => {
|
|
|
392
392
|
expect(pgdogToml).toContain('host = "0.0.0.0"');
|
|
393
393
|
expect(pgdogToml).toContain("port = 6432");
|
|
394
394
|
expect(pgdogToml).toContain('pooler_mode = "transaction"');
|
|
395
|
+
expect(pgdogToml).toContain("default_pool_size = 20");
|
|
395
396
|
expect(pgdogToml).toContain("[[databases]]");
|
|
396
397
|
expect(pgdogToml).toContain('name = "oke"');
|
|
397
398
|
expect(pgdogToml).toContain('host = "store-sql"');
|
|
398
399
|
expect(pgdogToml).toContain("port = 5432");
|
|
399
400
|
expect(pgdogToml).toContain('database_name = "oke"');
|
|
401
|
+
expect(pgdogToml).toContain("pool_size = 20");
|
|
400
402
|
|
|
401
403
|
const usersToml = buildPgDogUsersToml({
|
|
402
404
|
user: "oke",
|
|
@@ -936,6 +938,7 @@ describe("deriveInfrastructure", () => {
|
|
|
936
938
|
|
|
937
939
|
const pgdogToml = result.files.find((f) => f.path === "pgdog/pgdog.toml")!.content;
|
|
938
940
|
expect(pgdogToml).toContain('pooler_mode = "transaction"');
|
|
941
|
+
expect(pgdogToml).toContain("pool_size = 20");
|
|
939
942
|
expect(pgdogToml).toContain('host = "store-sql"');
|
|
940
943
|
expect(pgdogToml).not.toContain(fixedCreds["store.sql"].password);
|
|
941
944
|
|
|
@@ -957,7 +960,7 @@ describe("deriveInfrastructure", () => {
|
|
|
957
960
|
test("Dockerfile CMD is oke start", () => {
|
|
958
961
|
const df = emitDockerfile();
|
|
959
962
|
expect(df).toContain('CMD ["oke", "start"]');
|
|
960
|
-
expect(df).toContain("oven/bun:1.
|
|
963
|
+
expect(df).toContain("oven/bun:1.4");
|
|
961
964
|
});
|
|
962
965
|
|
|
963
966
|
test("writeDerivedFiles never writes compose.override.yml", async () => {
|
package/src/docker/dockerfile.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface DockerfileOptions {
|
|
|
19
19
|
*/
|
|
20
20
|
export function emitDockerfile(options: DockerfileOptions = {}): string {
|
|
21
21
|
const port = options.appPort ?? APP_PORT;
|
|
22
|
-
const base = options.baseImage ?? "oven/bun:1.
|
|
22
|
+
const base = options.baseImage ?? "oven/bun:1.4";
|
|
23
23
|
return `# generated by oke docker — do not edit
|
|
24
24
|
FROM ${base} AS deps
|
|
25
25
|
WORKDIR /app
|
|
@@ -35,12 +35,14 @@ export function buildPgDogToml(opts: {
|
|
|
35
35
|
'host = "0.0.0.0"',
|
|
36
36
|
"port = 6432",
|
|
37
37
|
'pooler_mode = "transaction"',
|
|
38
|
+
"default_pool_size = 20",
|
|
38
39
|
"",
|
|
39
40
|
"[[databases]]",
|
|
40
41
|
`name = ${tomlString(db)}`,
|
|
41
42
|
`host = ${tomlString(host)}`,
|
|
42
43
|
`port = ${port}`,
|
|
43
44
|
`database_name = ${tomlString(db)}`,
|
|
45
|
+
"pool_size = 20",
|
|
44
46
|
"",
|
|
45
47
|
].join("\n");
|
|
46
48
|
}
|
|
@@ -29,6 +29,10 @@ export async function openAnthropic(options: AiOpenOptions = {}): Promise<AiMode
|
|
|
29
29
|
const model = options.model ?? "claude-sonnet-4-20250514";
|
|
30
30
|
const baseUrl = (options.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
|
|
31
31
|
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
32
|
+
const preconnect = (fetchFn as { preconnect?: (href: string) => void }).preconnect;
|
|
33
|
+
if (typeof preconnect === "function") {
|
|
34
|
+
preconnect(baseUrl);
|
|
35
|
+
}
|
|
32
36
|
|
|
33
37
|
return {
|
|
34
38
|
driverId: "anthropic",
|
|
@@ -50,6 +54,7 @@ export async function openAnthropic(options: AiOpenOptions = {}): Promise<AiMode
|
|
|
50
54
|
...(system !== undefined ? { system } : {}),
|
|
51
55
|
messages,
|
|
52
56
|
}),
|
|
57
|
+
...(opts.signal !== undefined ? { signal: opts.signal } : {}),
|
|
53
58
|
});
|
|
54
59
|
const raw = (await res.json().catch(() => ({}))) as AnthropicMessagesResponse;
|
|
55
60
|
if (!res.ok) {
|
package/src/drivers/ai-ollama.ts
CHANGED
|
@@ -156,10 +156,7 @@ export async function openOllama(options: AiOpenOptions = {}): Promise<AiModelCl
|
|
|
156
156
|
const msg = raw.error ?? `ollama HTTP ${res.status}`;
|
|
157
157
|
throw new OllamaUnavailableError(`ollama: ${msg}`);
|
|
158
158
|
}
|
|
159
|
-
|
|
160
|
-
throw new OllamaUnavailableError("ollama: stream response has no body");
|
|
161
|
-
}
|
|
162
|
-
yield* readOllamaNdjson(res.body, opts.signal);
|
|
159
|
+
yield* readOllamaNdjson(res, opts.signal);
|
|
163
160
|
},
|
|
164
161
|
async embed(opts: AiEmbedOptions): Promise<AiEmbedResult> {
|
|
165
162
|
const resolvedModel = resolveOllamaModel(options, opts.model);
|
|
@@ -265,45 +262,45 @@ function parseOllamaToolCalls(
|
|
|
265
262
|
}
|
|
266
263
|
|
|
267
264
|
/**
|
|
268
|
-
* Parse Ollama NDJSON stream lines.
|
|
265
|
+
* Parse Ollama NDJSON stream lines via {@link Bun.JSONL.parseChunk}.
|
|
269
266
|
*
|
|
270
|
-
* @param
|
|
267
|
+
* @param res - Streaming response
|
|
271
268
|
* @param signal - Optional abort
|
|
272
269
|
*/
|
|
273
270
|
async function* readOllamaNdjson(
|
|
274
|
-
|
|
271
|
+
res: Response,
|
|
275
272
|
signal?: AbortSignal,
|
|
276
273
|
): AsyncGenerator<AiStreamChunk> {
|
|
277
|
-
const reader = body
|
|
274
|
+
const reader = res.body?.getReader();
|
|
275
|
+
if (!reader) {
|
|
276
|
+
throw new OllamaUnavailableError("ollama: stream response has no body");
|
|
277
|
+
}
|
|
278
278
|
const decoder = new TextDecoder();
|
|
279
|
-
let
|
|
279
|
+
let pending = "";
|
|
280
280
|
try {
|
|
281
281
|
while (true) {
|
|
282
|
-
if (signal?.aborted)
|
|
283
|
-
throw abortAsError(signal.reason);
|
|
284
|
-
}
|
|
282
|
+
if (signal?.aborted) throw abortAsError(signal.reason);
|
|
285
283
|
const { done, value } = await reader.read();
|
|
286
284
|
if (done) break;
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
for (const
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (chunk.done) {
|
|
300
|
-
yield { text: "", done: true };
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
} catch {
|
|
304
|
-
// ignore malformed lines
|
|
285
|
+
pending += decoder.decode(value, { stream: true });
|
|
286
|
+
const parsed = bunJsonl().parseChunk(pending);
|
|
287
|
+
pending = pending.slice(parsed.read);
|
|
288
|
+
for (const row of parsed.values) {
|
|
289
|
+
const chunk = row as OllamaChatResponse;
|
|
290
|
+
const delta = chunk.message?.content;
|
|
291
|
+
if (typeof delta === "string" && delta.length > 0) {
|
|
292
|
+
yield { text: delta };
|
|
293
|
+
}
|
|
294
|
+
if (chunk.done) {
|
|
295
|
+
yield { text: "", done: true };
|
|
296
|
+
return;
|
|
305
297
|
}
|
|
306
298
|
}
|
|
299
|
+
if (parsed.error) {
|
|
300
|
+
const nl = pending.indexOf("\n");
|
|
301
|
+
if (nl === -1) continue;
|
|
302
|
+
pending = pending.slice(nl + 1);
|
|
303
|
+
}
|
|
307
304
|
}
|
|
308
305
|
yield { text: "", done: true };
|
|
309
306
|
} finally {
|
|
@@ -311,6 +308,28 @@ async function* readOllamaNdjson(
|
|
|
311
308
|
}
|
|
312
309
|
}
|
|
313
310
|
|
|
311
|
+
interface BunJsonlParse {
|
|
312
|
+
readonly values: readonly unknown[];
|
|
313
|
+
readonly read: number;
|
|
314
|
+
readonly done: boolean;
|
|
315
|
+
readonly error: unknown;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function bunJsonl(): {
|
|
319
|
+
parse(text: string): unknown[];
|
|
320
|
+
parseChunk(text: string): BunJsonlParse;
|
|
321
|
+
} {
|
|
322
|
+
const jsonl = (
|
|
323
|
+
Bun as typeof Bun & {
|
|
324
|
+
JSONL?: { parse: (t: string) => unknown[]; parseChunk: (t: string) => BunJsonlParse };
|
|
325
|
+
}
|
|
326
|
+
).JSONL;
|
|
327
|
+
if (!jsonl) {
|
|
328
|
+
throw new Error("ollama: Bun.JSONL is required (Bun >= 1.4.0)");
|
|
329
|
+
}
|
|
330
|
+
return jsonl;
|
|
331
|
+
}
|
|
332
|
+
|
|
314
333
|
/**
|
|
315
334
|
* Probe Ollama — fail loud before the first completion.
|
|
316
335
|
*
|