20 Commits

Author SHA1 Message Date
8d1c839093 feat: sync to upstream OpenAPI v1.0.0 / Python SDK v2.4.3
Group A — gap fills:
- restore Client.GetWorkflowWorkerInfo (regression from v1.3.0;
  /v1/workflows/workers/whoami is still in the spec and is needed by
  callers running custom workers)
- add Client.GetChatCompletionFields, GetChatCompletionFieldOptions,
  GetChatCompletionFieldOptionsCounts (previously-missing observability
  fields API)

Group B — Python SDK v2.3.0..v2.4.3 sync:
- workflow.EncodedPayloadOption typed enum (offloaded / encrypted /
  encrypted-partial); replaces []string on NetworkEncodedInput.EncodingOptions.
  Wire-compatible refinement; source-incompatible for callers that built the
  slice as []string literals.
- workflow-connector integration: ConnectorSlot, ConnectorBindings,
  ConnectorExtensions, WorkflowExtensions, BuildConnectorExtensions(...)
  helper, ConnectorAuthTaskState, ConnectorAuthStatus constants.
  New Extensions map[string]any field on workflow.ExecutionRequest.
- HITL confirmation constants: conversation.Confirmation enum
  (ConfirmationAllow/Deny) and ConfirmationStatusPending/Allowed/Denied
  alongside the pre-existing ToolCallConfirmation type and
  tool_confirmations field.

No-op verification:
- ChatCompletionChoice.message remains singular per spec v1.0.0;
  Python's 'messages[]' rename was internal SDK shape, not wire format.

Tests: 297 (was 284), all green. go vet clean.
2026-04-28 13:04:04 +02:00
c5bb663bc7 chore: sync openapi spec to upstream v1.0.0; fix watcher
Upstream OpenAPI spec moved from v0.1.104 (7c0bb6af) to v1.0.0 (ff8a7389).
Only delta is removal of OCR confidence-score fields/types, which the SDK
never wrapped — no code changes required.

The watcher previously tried to commit hash/spec updates to main, but the
gitea→github mirror reverts them on every sync, leaving issue #1 with
stale hashes across multiple upstream releases. Watcher now skips the
push and instead refreshes the open tracking issue's body on each run,
posting a comment when upstream moves again while the issue is still open.
2026-04-28 12:37:56 +02:00
fc61bc42ad docs: update upstream reference to OpenAPI spec as primary source 2026-04-10 01:52:16 +02:00
9b7488183d ci: bump actions/checkout to v5 for Node.js 24 compat 2026-04-10 01:48:34 +02:00
78f7c745a7 ci: add OpenAPI spec change watcher workflow
Monitors upstream Mistral OpenAPI spec daily, opens a GitHub issue
with a unified diff when changes are detected. Includes curl failure
handling and explicit workflow permissions.
2026-04-10 01:45:39 +02:00
c028dfb0ed feat!: sync with Python SDK v2.3.0 — workflow registration model + remove workers
Add CodeDefinition, SignalDefinition, QueryDefinition, UpdateDefinition
types for workflow interface metadata. Update Registration struct with
DeploymentID, Definition, and CompatibleWithChatAssistant fields.
Deprecate TaskQueue in favor of DeploymentID.

BREAKING CHANGE: Remove GetWorkflowWorkerInfo and workflow.WorkerInfo —
the /v1/workflows/workers/whoami endpoint was removed upstream.
2026-04-03 18:53:15 +02:00
214fd02b3b docs: add v1.2.1 to upstream version table 2026-04-03 12:02:19 +02:00
3167966b98 chore: move module path to github.com/VikingOwl91/mistral-go-sdk
Public discoverability on pkg.go.dev. Also fixes stream tool call
test fixture to match real Mistral API responses (finish_reason, usage).
2026-04-03 12:01:11 +02:00
e22732aa7c docs: update README with Workflows API, correct method/test counts 2026-04-02 16:58:16 +02:00
6928b9f1c9 chore: bump version to v1.2.0, update changelog and docs 2026-04-02 16:52:26 +02:00
0ab8064a06 feat(batch): add DeleteBatchJob method 2026-04-02 16:51:51 +02:00
c5b0011e30 feat: add workflow runs, schedules, and workers service methods 2026-04-02 16:49:35 +02:00
dc30e09c77 feat: add workflow events, deployments, and metrics service methods 2026-04-02 16:47:34 +02:00
3b0530a409 feat: add workflow execution service methods and WorkflowEventStream
14 execution service methods (Get, History, Stream, Signal, Query,
Update, Terminate, Cancel, Reset, BatchCancel, BatchTerminate, TraceOTel,
TraceSummary, TraceEvents), WorkflowEventStream with envelope unwrapping,
and ExecuteWorkflowAndWait with isTerminal polling. Extends StreamPayload
with WorkflowContext and BrokerSequence fields.
2026-04-02 16:42:35 +02:00
29aa8e0de1 feat: add workflows CRUD and registration service methods 2026-04-02 16:36:29 +02:00
910970f45e feat(workflow): add deployment, metrics, run, schedule, and registration types 2026-04-02 16:31:01 +02:00
a699495fc2 feat(workflow): add sealed Event interface with 17 types and UnmarshalEvent
Implements EventType/EventSource/Scope enums, eventBase struct, 17 concrete
event types with typed attributes for Started/Completed/Failed, UnknownEvent
fallback, UnmarshalEvent dispatcher, and SSE envelope types.
2026-04-02 16:27:39 +02:00
a41bf39325 feat(workflow): add package scaffold, core CRUD types, and execution types 2026-04-02 16:24:08 +02:00
58712f8364 docs: add workflows API implementation plan
10-task TDD plan covering workflow/ types package, 8 service files,
WorkflowEventStream, sealed Event interface, DeleteBatchJob, version
bump, and changelog update.
2026-04-02 16:15:57 +02:00
b2a1f141e0 docs: add workflows API integration design spec
Design spec for integrating Mistral Python SDK v2.2.0 changes into the
Go SDK v1.2.0. Covers Workflows API (37 methods across 8 sub-resources),
DeleteBatchJob addition, sealed event interface with 17 variants, and
SSE streaming with StreamPayload envelope.
2026-04-02 16:05:35 +02:00
96 changed files with 26789 additions and 82 deletions

132
.github/workflows/watch-openapi.yml vendored Normal file
View File

@@ -0,0 +1,132 @@
name: Watch Mistral OpenAPI Spec
on:
schedule:
# Runs daily at 06:00 UTC
- cron: "0 6 * * *"
workflow_dispatch: # manual trigger for testing
permissions:
contents: read
issues: write
jobs:
check-spec:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Fetch and compare spec
id: check
run: |
if ! curl --fail -sL https://raw.githubusercontent.com/mistralai/platform-docs-public/main/openapi.yaml -o new-spec.yaml; then
echo "::error::Failed to download OpenAPI spec"
exit 1
fi
NEW_HASH=$(sha256sum new-spec.yaml | cut -d' ' -f1)
if [ -f .openapi-hash ]; then
OLD_HASH=$(cat .openapi-hash)
else
OLD_HASH="none"
fi
echo "old=$OLD_HASH" >> "$GITHUB_OUTPUT"
echo "new=$NEW_HASH" >> "$GITHUB_OUTPUT"
if [ "$NEW_HASH" != "$OLD_HASH" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
# Diff against the in-tree baseline (.openapi-spec.yaml).
# Maintainer commits a fresh baseline when addressing the issue.
if [ -f .openapi-spec.yaml ]; then
diff -u .openapi-spec.yaml new-spec.yaml > /tmp/spec-diff.txt || true
else
echo "No baseline .openapi-spec.yaml in repo - commit one to enable diffs." > /tmp/spec-diff.txt
fi
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
- name: Open or refresh tracking issue
if: steps.check.outputs.changed == 'true'
uses: actions/github-script@v7
env:
OLD_HASH: ${{ steps.check.outputs.old }}
NEW_HASH: ${{ steps.check.outputs.new }}
with:
script: |
const fs = require('fs');
let diff = fs.readFileSync('/tmp/spec-diff.txt', 'utf8');
// Truncate if too long for issue body (GitHub issue body limit is 65536 chars)
if (diff.length > 50000) {
diff = diff.substring(0, 50000) + '\n\n... (truncated - view full spec for details)';
}
const oldHash = process.env.OLD_HASH;
const newHash = process.env.NEW_HASH;
const body =
`The upstream OpenAPI spec has changed and the SDK is out of sync.\n\n` +
`**Stored hash (.openapi-hash):** \`${oldHash}\`\n` +
`**Current upstream hash:** \`${newHash}\`\n` +
`_Last refreshed: ${new Date().toISOString()}_\n\n` +
`[View upstream spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml)\n\n` +
`### Diff vs in-tree baseline\n\`\`\`diff\n${diff}\n\`\`\`\n\n` +
`### TODO\n` +
`- [ ] Review spec changes\n` +
`- [ ] Update SDK types if needed\n` +
`- [ ] Run tests\n` +
`- [ ] Bump \`.openapi-hash\` and refresh \`.openapi-spec.yaml\` in same commit\n` +
`- [ ] Close this issue\n\n` +
`<sub>This issue body is rewritten by the watcher each run, so the diff and hashes always reflect the latest upstream state.</sub>`;
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'openapi-update',
state: 'open'
});
if (issues.data.length === 0) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Mistral OpenAPI spec has changed',
body,
labels: ['openapi-update']
});
return;
}
// Refresh the existing open issue so the diff/hashes never go stale.
const issue = issues.data[0];
// Detect whether upstream moved *again* since the last refresh by
// parsing the previous "Current upstream hash" out of the issue body.
const marker = /\*\*Current upstream hash:\*\* `([a-f0-9]{64})`/;
const prev = (issue.body || '').match(marker);
const movedAgain = prev && prev[1] !== newHash;
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body
});
if (movedAgain) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body:
`Upstream spec moved again since this issue was last refreshed.\n\n` +
`Previous upstream hash: \`${prev[1]}\`\n` +
`New upstream hash: \`${newHash}\`\n\n` +
`Issue body has been updated to the latest diff.`
});
}

1
.openapi-hash Normal file
View File

@@ -0,0 +1 @@
ff8a7389b7a4e61145561361537aae37c49f7e2dcf7c4f79f41e80a30b484cc3

19660
.openapi-spec.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,150 @@
## v1.4.0 — 2026-04-28
Spec/SDK alignment pass after upstream OpenAPI moved to v1.0.0 and
Python SDK shipped v2.3.0..v2.4.3. RAG ingestion-pipeline beta surface
(Python v2.4.3) intentionally deferred until the dust settles upstream.
### Added
- **`Client.GetWorkflowWorkerInfo`** — restores the
`GET /v1/workflows/workers/whoami` endpoint that was removed in v1.3.0.
The endpoint is still in the spec and is needed by callers running
custom workers that connect their own scheduler.
(`workflow.WorkerInfo` type.)
- **Observability fields API** — three GETs missing since the
observability surface was first added:
- `Client.GetChatCompletionFields` (`/v1/observability/chat-completion-fields`)
- `Client.GetChatCompletionFieldOptions` (`…/{field}/options?operator=…`)
- `Client.GetChatCompletionFieldOptionsCounts` (`…/{field}/options-counts`)
- new types: `observability.BaseFieldDefinition`, `FieldGroup`,
`ChatCompletionFields`, `ChatCompletionFieldOptions`,
`FieldOptionCountsRequest`, `FieldOptionCounts`, `FieldOptionCountItem`,
plus `FieldType` and `FieldOperator` typed enums.
- **Workflow payload encoding constants** — `workflow.EncodedPayloadOption`
with `EncodedPayloadOffloaded`, `EncodedPayloadEncrypted`,
`EncodedPayloadEncryptedPartial`. Wire-compatible refinement of the
pre-existing `[]string` field on `NetworkEncodedInput`.
(Mirrors Python SDK v2.4.0.)
- **Workflow ↔ connector integration** (Python SDK v2.4.2):
- `workflow.ConnectorSlot`, `ConnectorBindings`, `ConnectorExtensions`,
`WorkflowExtensions` types.
- `workflow.BuildConnectorExtensions(slots …)` helper that produces the
nested map expected at `ExecutionRequest.Extensions["mistralai"]`.
- `workflow.ConnectorAuthTaskState` + `ConnectorAuthStatus` constants
for parsing payloads emitted by the `connector-auth` custom task event.
- New `Extensions map[string]any` field on `workflow.ExecutionRequest`.
- **HITL (human-in-the-loop) confirmation constants** — typed values
alongside the pre-existing `conversation.ToolCallConfirmation` and
`tool_confirmations` field:
- `conversation.Confirmation` with `ConfirmationAllow` / `ConfirmationDeny`
for the reply side.
- `ConfirmationStatusPending` / `ConfirmationStatusAllowed` /
`ConfirmationStatusDenied` for `FunctionCallEvent.ConfirmationStatus`
and `FunctionCallEntry.ConfirmationStatus` (already present as
untyped strings).
### Changed
- `workflow.NetworkEncodedInput.EncodingOptions` is now
`[]EncodedPayloadOption` (string-typed alias). JSON wire format
unchanged; existing call sites that passed `[]string{"offloaded"}`
need to switch to `[]workflow.EncodedPayloadOption{workflow.EncodedPayloadOffloaded}`
or the typed constants directly.
- Tracking upstream Mistral OpenAPI spec **v1.0.0** (was v0.1.104).
Only spec delta in this window was the removal of OCR confidence-score
fields (`OCRPageObject.confidence_scores`,
`OCRRequest.confidence_scores_granularity`,
`OCRTableObject.word_confidence_scores`, plus the `OCRConfidenceScore`
and `OCRPageConfidenceScores` schemas), none of which this SDK exposed.
### Fixed (CI)
- `watch-openapi.yml` no longer attempts to commit `.openapi-hash` /
`.openapi-spec.yaml` to `main`. The push was being silently reverted
by an upstream mirror, leaving the tracking issue stale across
multiple upstream releases. The watcher now refreshes the open
tracking issue's body on each run so the diff and hashes always
reflect the current upstream state, and posts a comment when the
spec moves again while the issue is still open.
## v1.3.0 — 2026-04-03
Upstream sync with Python SDK v2.3.0. Updates workflow registration model
to reflect the managed deployment architecture and removes the deprecated
workers endpoint.
### Added
- **`workflow.CodeDefinition`** — workflow interface metadata type with
input/output schemas, signal/query/update handler definitions,
determinism flag, and execution timeout.
- **`workflow.SignalDefinition`**, **`QueryDefinition`**,
**`UpdateDefinition`** — handler descriptor types.
- **`Registration.Definition`** — code definition field on workflow
registrations.
- **`Registration.DeploymentID`** — replaces the worker/task-queue model
with managed deployment references.
- **`Registration.CompatibleWithChatAssistant`** — flag for chat assistant
compatibility.
### Deprecated
- **`Registration.TaskQueue`** — use `DeploymentID` instead. Will be
removed in a future release.
### Removed (breaking)
- **`GetWorkflowWorkerInfo`** — the `/v1/workflows/workers/whoami` endpoint
was removed upstream.
- **`workflow.WorkerInfo`** — type no longer exists in the API.
## v1.2.1 — 2026-04-03
Move module path to `github.com/VikingOwl91/mistral-go-sdk` for public
discoverability on pkg.go.dev.
### Changed
- Module path changed from `somegit.dev/vikingowl/mistral-go-sdk` to
`github.com/VikingOwl91/mistral-go-sdk`.
### Fixed
- `TestChatCompleteStream_WithToolCalls` fixture now includes `finish_reason`
and `usage` to match real Mistral API responses.
## v1.2.0 — 2026-04-02
Upstream sync with Python SDK v2.2.0. Adds Workflows API and DeleteBatchJob.
### Added
- **Workflows API** (new `workflow/` package) — complete workflow orchestration
support with 37 service methods across 8 sub-resources:
- **Workflows CRUD** — `ListWorkflows`, `GetWorkflow`, `UpdateWorkflow`,
`ArchiveWorkflow`, `UnarchiveWorkflow`, `ExecuteWorkflow`,
`ExecuteWorkflowAndWait`.
- **Registrations** — `ListWorkflowRegistrations`, `GetWorkflowRegistration`,
`ExecuteWorkflowRegistration` (deprecated).
- **Executions** — `GetWorkflowExecution`, `GetWorkflowExecutionHistory`,
`StreamWorkflowExecution`, `SignalWorkflowExecution`,
`QueryWorkflowExecution`, `UpdateWorkflowExecution`,
`TerminateWorkflowExecution`, `CancelWorkflowExecution`,
`ResetWorkflowExecution`, `BatchCancelWorkflowExecutions`,
`BatchTerminateWorkflowExecutions`.
- **Trace** — `GetWorkflowExecutionTraceOTel`,
`GetWorkflowExecutionTraceSummary`, `GetWorkflowExecutionTraceEvents`.
- **Events** — `StreamWorkflowEvents`, `ListWorkflowEvents`.
- **Deployments** — `ListWorkflowDeployments`, `GetWorkflowDeployment`.
- **Metrics** — `GetWorkflowMetrics`.
- **Runs** — `ListWorkflowRuns`, `GetWorkflowRun`, `GetWorkflowRunHistory`.
- **Schedules** — `ListWorkflowSchedules`, `ScheduleWorkflow`,
`UnscheduleWorkflow`.
- **Workers** — `GetWorkflowWorkerInfo`.
- **`WorkflowEventStream`** — typed SSE stream wrapper with `StreamPayload`
envelope, sealed `Event` interface (17 concrete types + `UnknownEvent`).
- **`DeleteBatchJob`** — delete a batch job by ID.
## v1.1.0 — 2026-03-24
Upstream sync with Python SDK v2.1.3. Adds Connectors, Audio Speech/Voices, and Observability (beta).

View File

@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project
Idiomatic Go SDK for the Mistral AI API. Module path: `somegit.dev/vikingowl/mistral-go-sdk`. Requires Go 1.26+. Zero external dependencies (stdlib only). Tracks the upstream [Mistral Python SDK](https://github.com/mistralai/client-python) as reference for API surface and type definitions.
Idiomatic Go SDK for the Mistral AI API. Module path: `github.com/VikingOwl91/mistral-go-sdk`. Requires Go 1.26+. Zero external dependencies (stdlib only). Tracks the [official Mistral OpenAPI spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml) as primary reference for API surface and type definitions, with the [Mistral Python SDK](https://github.com/mistralai/client-python) as secondary reference for implementation patterns. A daily GitHub Action monitors the OpenAPI spec for changes.
## Repository layout
@@ -34,7 +34,7 @@ No Makefile, linter config, or code generation tooling — standard `go test` /
### Two-layer design: types in sub-packages, methods on `*Client`
Sub-packages (`chat/`, `agents/`, `conversation/`, `embedding/`, `model/`, `file/`, `finetune/`, `batch/`, `ocr/`, `audio/`, `library/`, `moderation/`, `classification/`, `fim/`) are **types-only** — they define request/response structs and enums but contain no HTTP logic. All service methods live on `*Client` in the root package, prefix-namespaced by domain (e.g. `ChatComplete`, `AgentsComplete`, `CreateFineTuningJob`, `UploadFile`).
Sub-packages (`chat/`, `agents/`, `conversation/`, `embedding/`, `model/`, `file/`, `finetune/`, `batch/`, `ocr/`, `audio/`, `library/`, `moderation/`, `classification/`, `fim/`, `connector/`, `observability/`, `workflow/`) are **types-only** — they define request/response structs and enums but contain no HTTP logic. All service methods live on `*Client` in the root package, prefix-namespaced by domain (e.g. `ChatComplete`, `AgentsComplete`, `CreateFineTuningJob`, `UploadFile`).
### HTTP internals (request.go)

View File

@@ -3,7 +3,7 @@
The most complete Go client for the [Mistral AI API](https://docs.mistral.ai/).
<!-- Badges -->
[![Go Reference](https://pkg.go.dev/badge/somegit.dev/vikingowl/mistral-go-sdk.svg)](https://pkg.go.dev/somegit.dev/vikingowl/mistral-go-sdk)
[![Go Reference](https://pkg.go.dev/badge/github.com/VikingOwl91/mistral-go-sdk.svg)](https://pkg.go.dev/github.com/VikingOwl91/mistral-go-sdk)
![Go Version](https://img.shields.io/badge/go-1.26-blue)
![License](https://img.shields.io/badge/license-MIT-green)
@@ -11,20 +11,20 @@ The most complete Go client for the [Mistral AI API](https://docs.mistral.ai/).
**Zero dependencies.** The entire SDK — including tests — uses only the Go standard library. No `go.sum`, no transitive dependency tree to audit, no version conflicts, no supply chain risk.
**Full API coverage.** 116 methods across every Mistral endpoint — including Connectors, Audio Speech/Voices, Conversations, Agents CRUD, Libraries, OCR, Observability, Fine-tuning, and Batch Jobs. No other Go SDK covers Conversations, Connectors, or Observability.
**Full API coverage.** 169 methods across every Mistral endpoint — including Workflows (with worker introspection and connector bindings), Connectors, Audio Speech/Voices, Conversations (including human-in-the-loop tool confirmations), Agents CRUD, Libraries, OCR, Observability (events, judges, datasets, campaigns, fields), Fine-tuning, and Batch Jobs. No other Go SDK covers Workflows, Conversations, Connectors, or Observability.
**Typed streaming.** A generic pull-based `Stream[T]` iterator — no channels, no goroutines, no leaks. Just `Next()` / `Current()` / `Err()` / `Close()`.
**Forward-compatible.** Unknown types (`UnknownEntry`, `UnknownEvent`, `UnknownMessage`, `UnknownChunk`, `UnknownAgentTool`) capture raw JSON instead of returning errors. When Mistral ships a new message role or event type, your code keeps running — it doesn't panic.
**Forward-compatible.** Unknown types (`UnknownEntry`, `UnknownEvent`, `UnknownMessage`, `UnknownChunk`, `UnknownAgentTool`, workflow `UnknownEvent`) capture raw JSON instead of returning errors. When Mistral ships a new message role or event type, your code keeps running — it doesn't panic.
**Hand-written, not generated.** Idiomatic Go with sealed interfaces, discriminated unions, and functional options — not a Speakeasy/OpenAPI auto-gen dump with `any` everywhere.
**Test-driven.** 193 tests with race detection clean. Every endpoint tested against mock servers; integration tests against the real API.
**Test-driven.** 297 tests with race detection clean. Every endpoint tested against mock servers; integration tests against the real API.
## Install
```sh
go get somegit.dev/vikingowl/mistral-go-sdk
go get github.com/VikingOwl91/mistral-go-sdk
```
## Quick Start
@@ -39,8 +39,8 @@ import (
"fmt"
"log"
mistral "somegit.dev/vikingowl/mistral-go-sdk"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
mistral "github.com/VikingOwl91/mistral-go-sdk"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
func main() {
@@ -112,7 +112,7 @@ resp, err := client.ChatComplete(ctx, &chat.CompletionRequest{
### Conversations
```go
import "somegit.dev/vikingowl/mistral-go-sdk/conversation"
import "github.com/VikingOwl91/mistral-go-sdk/conversation"
resp, err := client.StartConversation(ctx, &conversation.StartRequest{
AgentID: "ag-your-agent-id",
@@ -132,7 +132,7 @@ for stream.Next() {
## API Coverage
116 public methods on `Client`, grouped by domain:
169 public methods on `Client`, grouped by domain:
| Domain | Methods |
|--------|---------|
@@ -146,7 +146,7 @@ for stream.Next() {
| **Files** | `UploadFile`, `ListFiles`, `GetFile`, `DeleteFile`, `GetFileContent`, `GetFileURL` |
| **Embeddings** | `CreateEmbeddings` |
| **Fine-tuning** | `CreateFineTuningJob`, `ListFineTuningJobs`, `GetFineTuningJob`, `CancelFineTuningJob`, `StartFineTuningJob`, `UpdateFineTunedModel`, `ArchiveFineTunedModel`, `UnarchiveFineTunedModel` |
| **Batch** | `CreateBatchJob`, `ListBatchJobs`, `GetBatchJob`, `CancelBatchJob` |
| **Batch** | `CreateBatchJob`, `ListBatchJobs`, `GetBatchJob`, `CancelBatchJob`, `DeleteBatchJob` |
| **OCR** | `OCR` |
| **Audio (transcription)** | `Transcribe`, `TranscribeStream` |
| **Audio (speech)** | `Speech`, `SpeechStream` |
@@ -156,8 +156,19 @@ for stream.Next() {
| **Classification** | `Classify`, `ClassifyChat` |
| **Observability (campaigns)** | `CreateCampaign`, `ListCampaigns`, `GetCampaign`, `DeleteCampaign`, `GetCampaignStatus`, `ListCampaignEvents` |
| **Observability (events)** | `SearchChatCompletionEvents`, `SearchChatCompletionEventIDs`, `GetChatCompletionEvent`, `GetSimilarChatCompletionEvents`, `JudgeChatCompletionEvent` |
| **Observability (fields)** | `GetChatCompletionFields`, `GetChatCompletionFieldOptions`, `GetChatCompletionFieldOptionsCounts` |
| **Observability (judges)** | `CreateJudge`, `ListJudges`, `GetJudge`, `UpdateJudge`, `DeleteJudge`, `JudgeConversation` |
| **Observability (datasets)** | `CreateDataset`, `ListDatasets`, `GetDataset`, `UpdateDataset`, `DeleteDataset`, `ExportDatasetToJSONL`, `ListDatasetRecords`, `CreateDatasetRecord`, `GetDatasetRecord`, `UpdateDatasetRecordPayload`, `UpdateDatasetRecordProperties`, `DeleteDatasetRecord`, `BulkDeleteDatasetRecords`, `JudgeDatasetRecord`, `ImportDatasetFromCampaign`, `ImportDatasetFromExplorer`, `ImportDatasetFromFile`, `ImportDatasetFromPlayground`, `ImportDatasetFromDataset`, `ListDatasetTasks`, `GetDatasetTask` |
| **Workflows (CRUD)** | `ListWorkflows`, `GetWorkflow`, `UpdateWorkflow`, `ArchiveWorkflow`, `UnarchiveWorkflow`, `ExecuteWorkflow`, `ExecuteWorkflowAndWait` |
| **Workflows (registrations)** | `ListWorkflowRegistrations`, `GetWorkflowRegistration`, `ExecuteWorkflowRegistration` |
| **Workflows (executions)** | `GetWorkflowExecution`, `GetWorkflowExecutionHistory`, `StreamWorkflowExecution`, `SignalWorkflowExecution`, `QueryWorkflowExecution`, `UpdateWorkflowExecution`, `TerminateWorkflowExecution`, `CancelWorkflowExecution`, `ResetWorkflowExecution`, `BatchCancelWorkflowExecutions`, `BatchTerminateWorkflowExecutions` |
| **Workflows (trace)** | `GetWorkflowExecutionTraceOTel`, `GetWorkflowExecutionTraceSummary`, `GetWorkflowExecutionTraceEvents` |
| **Workflows (events)** | `StreamWorkflowEvents`, `ListWorkflowEvents` |
| **Workflows (deployments)** | `ListWorkflowDeployments`, `GetWorkflowDeployment` |
| **Workflows (metrics)** | `GetWorkflowMetrics` |
| **Workflows (runs)** | `ListWorkflowRuns`, `GetWorkflowRun`, `GetWorkflowRunHistory` |
| **Workflows (schedules)** | `ListWorkflowSchedules`, `ScheduleWorkflow`, `UnscheduleWorkflow` |
| **Workflows (workers)** | `GetWorkflowWorkerInfo` |
## Comparison
@@ -176,6 +187,7 @@ There is no official Go SDK from Mistral AI (only Python and TypeScript). The ma
| Fine-tuning / Batch | Yes | No | No | No |
| OCR | Yes | No | No | Yes |
| Audio (transcription + TTS + voices) | Yes | No | No | No |
| Workflows API | Yes | No | No | No |
| Observability (beta) | Yes | No | No | No |
| Moderation / Classification | Yes | No | No | No |
| Vision (multimodal) | Yes | No | No | Yes |
@@ -225,13 +237,18 @@ if err != nil {
## Upstream Reference
This SDK tracks the [official Mistral Python SDK](https://github.com/mistralai/client-python)
as its upstream reference for API surface and type definitions.
This SDK tracks the [official Mistral OpenAPI spec](https://github.com/mistralai/platform-docs-public/blob/main/openapi.yaml) as its primary reference for API surface and type definitions. A daily GitHub Action monitors the spec for changes and opens an issue when updates are detected.
| SDK Version | Upstream Python SDK |
|-------------|---------------------|
| v1.1.0 | v2.1.3 |
| v1.0.0 | v2.0.4 |
The [Mistral Python SDK](https://github.com/mistralai/client-python) is used as a secondary reference for implementation patterns.
| SDK Version | Upstream Python SDK | Upstream OpenAPI |
|-------------|---------------------|------------------|
| v1.4.0 | v2.4.3 (excl. RAG ingestion-pipeline beta) | v1.0.0 |
| v1.3.0 | v2.3.0 | v0.1.104 |
| v1.2.1 | v2.2.0 | — |
| v1.2.0 | v2.2.0 | — |
| v1.1.0 | v2.1.3 | — |
| v1.0.0 | v2.0.4 | — |
## License

View File

@@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
// AgentTool is a sealed interface for agent tool types.

View File

@@ -3,7 +3,7 @@ package agents
import (
"encoding/json"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
// CompletionRequest represents an agents completion request.

View File

@@ -3,8 +3,8 @@ package mistral
import (
"context"
"somegit.dev/vikingowl/mistral-go-sdk/agents"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/agents"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
// AgentsComplete sends an agents completion request.

View File

@@ -8,8 +8,8 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/agents"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/agents"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
func TestAgentsComplete_Success(t *testing.T) {

View File

@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/agents"
"github.com/VikingOwl91/mistral-go-sdk/agents"
)
// CreateAgent creates a new agent.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/agents"
"github.com/VikingOwl91/mistral-go-sdk/agents"
)
func TestCreateAgent_Success(t *testing.T) {

View File

@@ -7,7 +7,7 @@ import (
"io"
"net/http"
"somegit.dev/vikingowl/mistral-go-sdk/audio"
"github.com/VikingOwl91/mistral-go-sdk/audio"
)
// Transcribe sends an audio file for transcription.

View File

@@ -8,7 +8,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/audio"
"github.com/VikingOwl91/mistral-go-sdk/audio"
)
func TestSpeech_Success(t *testing.T) {

View File

@@ -8,7 +8,7 @@ import (
"strings"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/audio"
"github.com/VikingOwl91/mistral-go-sdk/audio"
)
func TestTranscribe_WithFileURL(t *testing.T) {

View File

@@ -60,3 +60,10 @@ type ListParams struct {
Status []string
OrderBy *string
}
// DeleteResponse is the response from deleting a batch job.
type DeleteResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Deleted bool `json:"deleted"`
}

View File

@@ -7,7 +7,7 @@ import (
"strconv"
"strings"
"somegit.dev/vikingowl/mistral-go-sdk/batch"
"github.com/VikingOwl91/mistral-go-sdk/batch"
)
// CreateBatchJob creates a new batch inference job.
@@ -76,3 +76,12 @@ func (c *Client) CancelBatchJob(ctx context.Context, jobID string) (*batch.JobOu
}
return &resp, nil
}
// DeleteBatchJob deletes a batch job.
func (c *Client) DeleteBatchJob(ctx context.Context, jobID string) (*batch.DeleteResponse, error) {
var resp batch.DeleteResponse
if err := c.doJSON(ctx, "DELETE", fmt.Sprintf("/v1/batch/jobs/%s", jobID), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/batch"
"github.com/VikingOwl91/mistral-go-sdk/batch"
)
func TestCreateBatchJob_Success(t *testing.T) {
@@ -121,3 +121,30 @@ func TestCancelBatchJob_Success(t *testing.T) {
t.Errorf("got status %q", job.Status)
}
}
func TestDeleteBatchJob_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
t.Errorf("expected DELETE, got %s", r.Method)
}
if r.URL.Path != "/v1/batch/jobs/batch-123" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"id": "batch-123", "object": "batch", "deleted": true,
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.DeleteBatchJob(context.Background(), "batch-123")
if err != nil {
t.Fatal(err)
}
if resp.ID != "batch-123" {
t.Errorf("got id %q", resp.ID)
}
if !resp.Deleted {
t.Error("expected deleted=true")
}
}

View File

@@ -3,7 +3,7 @@ package mistral
import (
"context"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
// ChatComplete sends a chat completion request and returns the full response.

View File

@@ -8,7 +8,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
func TestChatComplete_Success(t *testing.T) {

View File

@@ -9,7 +9,7 @@ import (
"strings"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
func TestChatCompleteStream_Success(t *testing.T) {
@@ -165,6 +165,7 @@ func TestChatCompleteStream_WithToolCalls(t *testing.T) {
w.Header().Set("Content-Type", "text/event-stream")
flusher, _ := w.(http.Flusher)
toolCalls := chat.FinishReasonToolCalls
chunk := chat.CompletionChunk{
ID: "c",
Model: "m",
@@ -177,7 +178,9 @@ func TestChatCompleteStream_WithToolCalls(t *testing.T) {
Function: chat.FunctionCall{Name: "get_weather", Arguments: `{"city":"Paris"}`},
}},
},
FinishReason: &toolCalls,
}},
Usage: &chat.UsageInfo{PromptTokens: 10, CompletionTokens: 5, TotalTokens: 15},
}
data, _ := json.Marshal(chunk)
fmt.Fprintf(w, "data: %s\n\n", data)
@@ -199,10 +202,17 @@ func TestChatCompleteStream_WithToolCalls(t *testing.T) {
if !stream.Next() {
t.Fatalf("expected chunk, err: %v", stream.Err())
}
tc := stream.Current().Choices[0].Delta.ToolCalls
cur := stream.Current()
tc := cur.Choices[0].Delta.ToolCalls
if len(tc) != 1 || tc[0].Function.Name != "get_weather" {
t.Errorf("got tool calls %+v", tc)
}
if cur.Choices[0].FinishReason == nil || *cur.Choices[0].FinishReason != chat.FinishReasonToolCalls {
t.Errorf("expected finish_reason tool_calls, got %v", cur.Choices[0].FinishReason)
}
if cur.Usage == nil || cur.Usage.TotalTokens != 15 {
t.Errorf("expected usage with total_tokens=15, got %+v", cur.Usage)
}
}
func TestChatCompleteStream_APIError(t *testing.T) {

View File

@@ -1,6 +1,6 @@
package classification
import "somegit.dev/vikingowl/mistral-go-sdk/chat"
import "github.com/VikingOwl91/mistral-go-sdk/chat"
// Request represents a text classification request (/v1/classifications).
type Request struct {

View File

@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/connector"
"github.com/VikingOwl91/mistral-go-sdk/connector"
)
// CreateConnector registers a new MCP connector.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/connector"
"github.com/VikingOwl91/mistral-go-sdk/connector"
)
func TestCreateConnector_Success(t *testing.T) {

View File

@@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
// HandoffExecution controls tool call execution.
@@ -44,10 +44,30 @@ type CompletionArgs struct {
ToolChoice *chat.ToolChoiceMode `json:"tool_choice,omitempty"`
}
// Confirmation is a client decision on a pending tool call.
type Confirmation string
const (
ConfirmationAllow Confirmation = "allow"
ConfirmationDeny Confirmation = "deny"
)
// ConfirmationStatus values appear on FunctionCallEvent.ConfirmationStatus
// and FunctionCallEntry.ConfirmationStatus, reporting where in the
// human-in-the-loop flow a tool call currently sits.
const (
ConfirmationStatusPending = "pending"
ConfirmationStatusAllowed = "allowed"
ConfirmationStatusDenied = "denied"
)
// ToolCallConfirmation confirms or denies a pending tool call.
//
// Send a slice of these on AppendRequest.ToolConfirmations after receiving
// a function call event whose ConfirmationStatus is "pending".
type ToolCallConfirmation struct {
ToolCallID string `json:"tool_call_id"`
Confirmation string `json:"confirmation"` // "allow" or "deny"
Confirmation string `json:"confirmation"` // use ConfirmationAllow / ConfirmationDeny
}
// Inputs represents conversation inputs (text string or entry array).

View File

@@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
// Entry is a sealed interface for conversation history entries.

View File

@@ -3,7 +3,7 @@ package conversation
import (
"encoding/json"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
// StartRequest starts a new conversation.

View File

@@ -7,7 +7,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/conversation"
"github.com/VikingOwl91/mistral-go-sdk/conversation"
)
// StartConversation creates and starts a new conversation.

View File

@@ -8,7 +8,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/conversation"
"github.com/VikingOwl91/mistral-go-sdk/conversation"
)
func TestStartConversation_Success(t *testing.T) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,650 @@
# Workflows API Integration — Design Spec
**Date:** 2026-04-02
**Upstream:** Mistral Python SDK v2.2.0 (released 2026-03-31)
**SDK version:** v1.2.0
**Scope:** Full parity with Python SDK v2.2.0 changes since v2.1.3
## Summary
Add the Workflows API (37 new methods) and `DeleteBatchJob` (1 method) to the Go SDK.
This is purely additive — no breaking changes to existing API surface.
## New Package: `workflow/`
Types-only package following the two-layer architecture. 8 type files + `doc.go`.
### `workflow/doc.go`
Package documentation.
### `workflow/workflow.go` — Core CRUD types
```go
type Workflow struct {
ID string `json:"id"`
Name string `json:"name"`
DisplayName *string `json:"display_name,omitempty"`
Description *string `json:"description,omitempty"`
OwnerID string `json:"owner_id"`
WorkspaceID string `json:"workspace_id"`
AvailableInChatAssistant bool `json:"available_in_chat_assistant"`
Archived bool `json:"archived"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type WorkflowUpdateRequest struct {
DisplayName *string `json:"display_name,omitempty"`
Description *string `json:"description,omitempty"`
AvailableInChatAssistant *bool `json:"available_in_chat_assistant,omitempty"`
}
type WorkflowListResponse struct {
Workflows []Workflow `json:"workflows"`
NextCursor *string `json:"next_cursor,omitempty"`
}
type WorkflowListParams struct {
ActiveOnly *bool
IncludeShared *bool
AvailableInChatAssistant *bool
Archived *bool
Cursor *string
Limit *int
}
type WorkflowArchiveResponse struct {
ID string `json:"id"`
Archived bool `json:"archived"`
}
```
### `workflow/execution.go` — Execution types
```go
type ExecutionStatus string
const (
ExecutionRunning ExecutionStatus = "RUNNING"
ExecutionCompleted ExecutionStatus = "COMPLETED"
ExecutionFailed ExecutionStatus = "FAILED"
ExecutionCanceled ExecutionStatus = "CANCELED"
ExecutionTerminated ExecutionStatus = "TERMINATED"
ExecutionContinuedAsNew ExecutionStatus = "CONTINUED_AS_NEW"
ExecutionTimedOut ExecutionStatus = "TIMED_OUT"
ExecutionRetryingAfterErr ExecutionStatus = "RETRYING_AFTER_ERROR"
)
type ExecutionRequest struct {
ExecutionID *string `json:"execution_id,omitempty"`
Input map[string]any `json:"input,omitempty"`
EncodedInput *NetworkEncodedInput `json:"encoded_input,omitempty"`
WaitForResult bool `json:"wait_for_result,omitempty"`
TimeoutSeconds *float64 `json:"timeout_seconds,omitempty"`
CustomTracingAttributes map[string]string `json:"custom_tracing_attributes,omitempty"`
DeploymentName *string `json:"deployment_name,omitempty"`
}
type ExecutionResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result,omitempty"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
}
type NetworkEncodedInput struct {
B64Payload string `json:"b64payload"`
EncodingOptions []string `json:"encoding_options,omitempty"`
Empty bool `json:"empty,omitempty"`
}
type SignalInvocationBody struct {
Name string `json:"name"`
Input any `json:"input"`
}
type SignalResponse struct {
Message string `json:"message"` // default: "Signal accepted"
}
type QueryInvocationBody struct {
Name string `json:"name"`
Input any `json:"input,omitempty"`
}
type QueryResponse struct {
QueryName string `json:"query_name"`
Result any `json:"result"`
}
type UpdateInvocationBody struct {
Name string `json:"name"`
Input any `json:"input,omitempty"`
}
type UpdateResponse struct {
UpdateName string `json:"update_name"`
Result any `json:"result"`
}
// Trace response types
type TraceOTelResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
DataSource string `json:"data_source"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
OTelTraceID *string `json:"otel_trace_id,omitempty"`
OTelTraceData any `json:"otel_trace_data,omitempty"`
}
type TraceSummaryResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
SpanTree any `json:"span_tree,omitempty"`
}
type TraceEventsResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
Events []json.RawMessage `json:"events,omitempty"`
}
type TraceEventsParams struct {
MergeSameIDEvents *bool
IncludeInternalEvents *bool
}
type ResetInvocationBody struct {
EventID int `json:"event_id"`
Reason *string `json:"reason,omitempty"`
ExcludeSignals bool `json:"exclude_signals,omitempty"`
ExcludeUpdates bool `json:"exclude_updates,omitempty"`
}
type BatchExecutionBody struct {
ExecutionIDs []string `json:"execution_ids"`
}
type BatchExecutionResponse struct {
Results map[string]BatchExecutionResult `json:"results,omitempty"`
}
type BatchExecutionResult struct {
Status string `json:"status"`
Error *string `json:"error,omitempty"`
}
type StreamParams struct {
EventSource *EventSource
LastEventID *string
}
```
### `workflow/event.go` — Sealed event interface + 17 variants
Discriminator field: `event_type`
```go
type Event interface {
workflowEvent()
EventType() EventType
}
type EventType string
const (
EventWorkflowStarted EventType = "WORKFLOW_EXECUTION_STARTED"
EventWorkflowCompleted EventType = "WORKFLOW_EXECUTION_COMPLETED"
EventWorkflowFailed EventType = "WORKFLOW_EXECUTION_FAILED"
EventWorkflowCanceled EventType = "WORKFLOW_EXECUTION_CANCELED"
EventWorkflowContinuedAsNew EventType = "WORKFLOW_EXECUTION_CONTINUED_AS_NEW"
EventWorkflowTaskTimedOut EventType = "WORKFLOW_TASK_TIMED_OUT"
EventWorkflowTaskFailed EventType = "WORKFLOW_TASK_FAILED"
EventCustomTaskStarted EventType = "CUSTOM_TASK_STARTED"
EventCustomTaskInProgress EventType = "CUSTOM_TASK_IN_PROGRESS"
EventCustomTaskCompleted EventType = "CUSTOM_TASK_COMPLETED"
EventCustomTaskFailed EventType = "CUSTOM_TASK_FAILED"
EventCustomTaskTimedOut EventType = "CUSTOM_TASK_TIMED_OUT"
EventCustomTaskCanceled EventType = "CUSTOM_TASK_CANCELED"
EventActivityTaskStarted EventType = "ACTIVITY_TASK_STARTED"
EventActivityTaskCompleted EventType = "ACTIVITY_TASK_COMPLETED"
EventActivityTaskRetrying EventType = "ACTIVITY_TASK_RETRYING"
EventActivityTaskFailed EventType = "ACTIVITY_TASK_FAILED"
)
type EventSource string
const (
EventSourceDatabase EventSource = "DATABASE"
EventSourceLive EventSource = "LIVE"
)
type Scope string
const (
ScopeActivity Scope = "activity"
ScopeWorkflow Scope = "workflow"
ScopeAll Scope = "*"
)
```
Each concrete event type has common fields + type-specific attributes:
```go
// Common fields embedded in all event types
type eventBase struct {
ID string `json:"event_id"`
Timestamp int64 `json:"event_timestamp"`
RootWorkflowExecID string `json:"root_workflow_exec_id"`
ParentWorkflowExecID *string `json:"parent_workflow_exec_id"`
WorkflowExecID string `json:"workflow_exec_id"`
WorkflowRunID string `json:"workflow_run_id"`
WorkflowName string `json:"workflow_name"`
}
// Example concrete types:
type WorkflowExecutionStartedEvent struct {
eventBase
Attributes WorkflowStartedAttributes `json:"attributes"`
}
func (WorkflowExecutionStartedEvent) workflowEvent() {}
func (WorkflowExecutionStartedEvent) EventType() EventType { return EventWorkflowStarted }
type WorkflowExecutionCompletedEvent struct {
eventBase
Attributes WorkflowCompletedAttributes `json:"attributes"`
}
// ... pattern repeats for all 17 types
type UnknownEvent struct {
eventBase
RawType string
Raw json.RawMessage
}
```
SSE envelope types:
```go
type StreamPayload struct {
Stream string `json:"stream"`
Data json.RawMessage `json:"data"`
WorkflowContext StreamWorkflowContext `json:"workflow_context"`
BrokerSequence int `json:"broker_sequence"`
Timestamp *string `json:"timestamp,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
type StreamWorkflowContext struct {
Namespace string `json:"namespace"`
WorkflowName string `json:"workflow_name"`
WorkflowExecID string `json:"workflow_exec_id"`
ParentWorkflowExecID *string `json:"parent_workflow_exec_id,omitempty"`
RootWorkflowExecID *string `json:"root_workflow_exec_id,omitempty"`
}
func UnmarshalEvent(data json.RawMessage) (Event, error)
// Probes event_type discriminator, dispatches to concrete type.
// Unknown event_type returns UnknownEvent.
```
### `workflow/deployment.go`
```go
type Deployment struct {
ID string `json:"id"`
Name string `json:"name"`
IsActive bool `json:"is_active"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type DeploymentListResponse struct {
Deployments []Deployment `json:"deployments"`
}
type DeploymentListParams struct {
ActiveOnly *bool
WorkflowName *string
}
```
### `workflow/metrics.go`
```go
type Metrics struct {
ExecutionCount ScalarMetric `json:"execution_count"`
SuccessCount ScalarMetric `json:"success_count"`
ErrorCount ScalarMetric `json:"error_count"`
AverageLatencyMs ScalarMetric `json:"average_latency_ms"`
LatencyOverTime TimeSeriesMetric `json:"latency_over_time"`
RetryRate ScalarMetric `json:"retry_rate"`
}
type ScalarMetric struct {
Value float64 `json:"value"`
}
type TimeSeriesMetric struct {
Value [][]float64 `json:"value"`
}
type MetricsParams struct {
StartTime *string
EndTime *string
}
```
### `workflow/run.go`
```go
type Run struct {
ID string `json:"id"`
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
Status ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
}
type ListRunsResponse struct {
Runs []Run `json:"runs"`
NextPageToken *string `json:"next_page_token,omitempty"`
}
type RunListParams struct {
WorkflowIdentifier *string
Search *string
Status *string
PageSize *int
NextPageToken *string
}
```
### `workflow/schedule.go`
```go
type ScheduleRequest struct {
Schedule ScheduleDefinition `json:"schedule"`
WorkflowRegistrationID *string `json:"workflow_registration_id,omitempty"`
WorkflowIdentifier *string `json:"workflow_identifier,omitempty"`
ScheduleID *string `json:"schedule_id,omitempty"`
DeploymentName *string `json:"deployment_name,omitempty"`
}
type ScheduleDefinition struct {
Input any `json:"input"`
Calendars []ScheduleCalendar `json:"calendars,omitempty"`
Intervals []ScheduleInterval `json:"intervals,omitempty"`
CronExpressions []string `json:"cron_expressions,omitempty"`
Skip []ScheduleCalendar `json:"skip,omitempty"`
StartAt *string `json:"start_at,omitempty"`
EndAt *string `json:"end_at,omitempty"`
Jitter *string `json:"jitter,omitempty"`
TimeZoneName *string `json:"time_zone_name,omitempty"`
Policy *SchedulePolicy `json:"policy,omitempty"`
}
type ScheduleCalendar struct {
Second []ScheduleRange `json:"second,omitempty"`
Minute []ScheduleRange `json:"minute,omitempty"`
Hour []ScheduleRange `json:"hour,omitempty"`
DayOfMonth []ScheduleRange `json:"day_of_month,omitempty"`
Month []ScheduleRange `json:"month,omitempty"`
Year []ScheduleRange `json:"year,omitempty"`
DayOfWeek []ScheduleRange `json:"day_of_week,omitempty"`
Comment *string `json:"comment,omitempty"`
}
type ScheduleRange struct {
Start int `json:"start"`
End int `json:"end,omitempty"`
Step int `json:"step,omitempty"`
}
type ScheduleInterval struct {
Every string `json:"every"`
Offset *string `json:"offset,omitempty"`
}
type SchedulePolicy struct {
CatchupWindowSeconds int `json:"catchup_window_seconds,omitempty"`
Overlap *int `json:"overlap,omitempty"`
PauseOnFailure bool `json:"pause_on_failure,omitempty"`
}
type ScheduleResponse struct {
ScheduleID string `json:"schedule_id"`
}
type ScheduleListResponse struct {
Schedules []Schedule `json:"schedules"`
}
type Schedule struct {
ScheduleID string `json:"schedule_id"`
Definition ScheduleDefinition `json:"definition"`
WorkflowName string `json:"workflow_name"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
```
### `workflow/registration.go`
```go
type Registration struct {
ID string `json:"id"`
WorkflowID string `json:"workflow_id"`
TaskQueue string `json:"task_queue"`
Workflow *Workflow `json:"workflow,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type RegistrationListResponse struct {
Registrations []Registration `json:"registrations"`
NextCursor *string `json:"next_cursor,omitempty"`
}
type RegistrationListParams struct {
WorkflowID *string
TaskQueue *string
ActiveOnly *bool
IncludeShared *bool
WorkflowSearch *string
Archived *bool
WithWorkflow *bool
AvailableInChatAssistant *bool
Limit *int
Cursor *string
}
type RegistrationGetParams struct {
WithWorkflow *bool
IncludeShared *bool
}
type WorkerInfo struct {
SchedulerURL string `json:"scheduler_url"`
Namespace string `json:"namespace"`
TLS bool `json:"tls"`
}
```
## Service Methods (root package)
### `workflows.go` — 10 methods
| Method | HTTP | Path |
|--------|------|------|
| `ListWorkflows` | GET | `/v1/workflows` |
| `GetWorkflow` | GET | `/v1/workflows/{id}` |
| `UpdateWorkflow` | PUT | `/v1/workflows/{id}` |
| `ArchiveWorkflow` | PUT | `/v1/workflows/{id}/archive` |
| `UnarchiveWorkflow` | PUT | `/v1/workflows/{id}/unarchive` |
| `ExecuteWorkflow` | POST | `/v1/workflows/{id}/execute` |
| `ListWorkflowRegistrations` | GET | `/v1/workflows/registrations` |
| `GetWorkflowRegistration` | GET | `/v1/workflows/registrations/{id}` |
| `ExecuteWorkflowRegistration` | POST | `/v1/workflows/registrations/{id}/execute` |
| `ExecuteWorkflowAndWait` | (composite) | execute + poll |
`ExecuteWorkflowRegistration` is deprecated (doc comment only).
`ExecuteWorkflowAndWait` calls `ExecuteWorkflow`, then polls `GetWorkflowExecution`
in a loop until status is terminal or context is canceled.
### `workflows_executions.go` — 14 methods
| Method | HTTP | Path |
|--------|------|------|
| `GetWorkflowExecution` | GET | `/v1/workflows/executions/{id}` |
| `GetWorkflowExecutionHistory` | GET | `/v1/workflows/executions/{id}/history` |
| `StreamWorkflowExecution` | GET (SSE) | `/v1/workflows/executions/{id}/stream` |
| `SignalWorkflowExecution` | POST | `/v1/workflows/executions/{id}/signals` |
| `QueryWorkflowExecution` | POST | `/v1/workflows/executions/{id}/queries` |
| `UpdateWorkflowExecution` | POST | `/v1/workflows/executions/{id}/updates` |
| `TerminateWorkflowExecution` | POST (204) | `/v1/workflows/executions/{id}/terminate` |
| `CancelWorkflowExecution` | POST (204) | `/v1/workflows/executions/{id}/cancel` |
| `ResetWorkflowExecution` | POST (204) | `/v1/workflows/executions/{id}/reset` |
| `BatchCancelWorkflowExecutions` | POST | `/v1/workflows/executions/cancel` |
| `BatchTerminateWorkflowExecutions` | POST | `/v1/workflows/executions/terminate` |
| `GetWorkflowExecutionTraceOTel` | GET | `/v1/workflows/executions/{id}/trace/otel` |
| `GetWorkflowExecutionTraceSummary` | GET | `/v1/workflows/executions/{id}/trace/summary` |
| `GetWorkflowExecutionTraceEvents` | GET | `/v1/workflows/executions/{id}/trace/events` |
Also contains `WorkflowEventStream` type (wraps `Stream[json.RawMessage]`,
dispatches via `workflow.UnmarshalEvent`).
### `workflows_events.go` — 2 methods
| Method | HTTP | Path |
|--------|------|------|
| `StreamWorkflowEvents` | GET (SSE) | `/v1/workflows/events/stream` |
| `ListWorkflowEvents` | GET | `/v1/workflows/events/list` |
### `workflows_deployments.go` — 2 methods
| Method | HTTP | Path |
|--------|------|------|
| `ListWorkflowDeployments` | GET | `/v1/workflows/deployments` |
| `GetWorkflowDeployment` | GET | `/v1/workflows/deployments/{id}` |
### `workflows_metrics.go` — 1 method
| Method | HTTP | Path |
|--------|------|------|
| `GetWorkflowMetrics` | GET | `/v1/workflows/{name}/metrics` |
### `workflows_runs.go` — 3 methods
| Method | HTTP | Path |
|--------|------|------|
| `ListWorkflowRuns` | GET | `/v1/workflows/runs` |
| `GetWorkflowRun` | GET | `/v1/workflows/runs/{id}` |
| `GetWorkflowRunHistory` | GET | `/v1/workflows/runs/{id}/history` |
### `workflows_schedules.go` — 3 methods
| Method | HTTP | Path |
|--------|------|------|
| `ListWorkflowSchedules` | GET | `/v1/workflows/schedules` |
| `ScheduleWorkflow` | POST | `/v1/workflows/schedules` |
| `UnscheduleWorkflow` | DELETE | `/v1/workflows/schedules/{id}` |
### `workflows_workers.go` — 1 method
| Method | HTTP | Path |
|--------|------|------|
| `GetWorkflowWorkerInfo` | GET | `/v1/workflows/workers/whoami` |
### `batch_api.go` — 1 new method
| Method | HTTP | Path |
|--------|------|------|
| `DeleteBatchJob` | DELETE | `/v1/batch/jobs/{id}` |
New type in `batch/`: `DeleteResponse { ID, Object, Deleted }`.
## Streaming Design
`WorkflowEventStream` wraps `Stream[json.RawMessage]` like `EventStream` does for conversations.
SSE data arrives as `StreamPayload` envelope:
```json
{
"stream": "...",
"data": { "event_type": "WORKFLOW_EXECUTION_COMPLETED", ... },
"workflow_context": { ... },
"broker_sequence": 42,
"timestamp": "...",
"metadata": {}
}
```
`WorkflowEventStream.Next()`:
1. Read next SSE `data:` line via inner `Stream[json.RawMessage]`
2. Unmarshal into `workflow.StreamPayload`
3. Dispatch `payload.Data` via `workflow.UnmarshalEvent` (probes `event_type`)
4. Expose both `Current() workflow.Event` and `CurrentPayload() *workflow.StreamPayload`
Both `StreamWorkflowExecution` and `StreamWorkflowEvents` use GET (not POST)
with SSE response. They use `doStream` without a request body — the stream method
needs to support GET + query params (verify `doStream` handles nil body for GET).
## Testing
One test file per service file. `httptest.NewServer` with inline handlers. Stdlib only.
Key scenarios:
- Query param encoding for list/filter endpoints
- PUT body marshaling for update/archive
- 204 no-body responses for terminate/cancel/reset
- 202 response for signal
- SSE streaming with StreamPayload envelope + event type dispatch
- UnknownEvent forward compatibility
- ExecuteWorkflowAndWait polling loop (mock multiple get-execution responses)
- Sealed interface UnmarshalEvent for all 17 event types + unknown
- Batch operations with map response
## Version & Docs
- Bump version constant in `mistral.go` to `1.2.0`
- Update `CLAUDE.md` sub-packages list to include `workflow/`
- Update `CHANGELOG.md` with v1.2.0 entry
- Upstream sync reference: Python SDK v2.2.0
## Non-Goals
- No pagination helpers (cursor chaining) — callers manage pagination manually, same as existing endpoints
- No traceparent injection hook — Go callers manage their own tracing headers
- No `execute_workflow_and_wait_async` — Go has context cancellation instead
- No WebSocket/realtime workflow support (not in Python SDK either)

View File

@@ -1,6 +1,6 @@
package embedding
import "somegit.dev/vikingowl/mistral-go-sdk/chat"
import "github.com/VikingOwl91/mistral-go-sdk/chat"
// Dtype specifies the data type of output embeddings.
type Dtype string

View File

@@ -3,7 +3,7 @@ package mistral
import (
"context"
"somegit.dev/vikingowl/mistral-go-sdk/embedding"
"github.com/VikingOwl91/mistral-go-sdk/embedding"
)
// CreateEmbeddings sends an embedding request and returns the response.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/embedding"
"github.com/VikingOwl91/mistral-go-sdk/embedding"
)
func TestCreateEmbeddings_Success(t *testing.T) {

View File

@@ -5,9 +5,9 @@ import (
"fmt"
"log"
mistral "somegit.dev/vikingowl/mistral-go-sdk"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"somegit.dev/vikingowl/mistral-go-sdk/embedding"
mistral "github.com/VikingOwl91/mistral-go-sdk"
"github.com/VikingOwl91/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/embedding"
)
func ExampleNewClient() {

View File

@@ -8,7 +8,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/file"
"github.com/VikingOwl91/mistral-go-sdk/file"
)
// UploadFile uploads a file for use with fine-tuning, batch, or OCR.

View File

@@ -9,7 +9,7 @@ import (
"strings"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/file"
"github.com/VikingOwl91/mistral-go-sdk/file"
)
func TestUploadFile_Success(t *testing.T) {

View File

@@ -3,8 +3,8 @@ package mistral
import (
"context"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"somegit.dev/vikingowl/mistral-go-sdk/fim"
"github.com/VikingOwl91/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/fim"
)
// FIMComplete sends a Fill-In-the-Middle completion request.

View File

@@ -8,8 +8,8 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"somegit.dev/vikingowl/mistral-go-sdk/fim"
"github.com/VikingOwl91/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/fim"
)
func TestFIMComplete_Success(t *testing.T) {

View File

@@ -7,7 +7,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/finetune"
"github.com/VikingOwl91/mistral-go-sdk/finetune"
)
// CreateFineTuningJob creates a new fine-tuning job.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/finetune"
"github.com/VikingOwl91/mistral-go-sdk/finetune"
)
func TestCreateFineTuningJob_Success(t *testing.T) {

2
go.mod
View File

@@ -1,3 +1,3 @@
module somegit.dev/vikingowl/mistral-go-sdk
module github.com/VikingOwl91/mistral-go-sdk
go 1.26

View File

@@ -8,8 +8,8 @@ import (
"strings"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"somegit.dev/vikingowl/mistral-go-sdk/embedding"
"github.com/VikingOwl91/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/embedding"
)
func integrationClient(t *testing.T) *Client {

View File

@@ -7,7 +7,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/library"
"github.com/VikingOwl91/mistral-go-sdk/library"
)
// CreateLibrary creates a new document library.

View File

@@ -8,7 +8,7 @@ import (
"strings"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/library"
"github.com/VikingOwl91/mistral-go-sdk/library"
)
func newLibraryJSON() map[string]any {

View File

@@ -6,7 +6,7 @@ import (
)
// Version is the SDK version string.
const Version = "1.1.0"
const Version = "1.3.0"
const (
defaultBaseURL = "https://api.mistral.ai"

View File

@@ -4,7 +4,7 @@ import (
"context"
"net/url"
"somegit.dev/vikingowl/mistral-go-sdk/model"
"github.com/VikingOwl91/mistral-go-sdk/model"
)
// ListModels returns a list of available models.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/model"
"github.com/VikingOwl91/mistral-go-sdk/model"
)
func TestListModels_Success(t *testing.T) {

View File

@@ -1,6 +1,6 @@
package moderation
import "somegit.dev/vikingowl/mistral-go-sdk/chat"
import "github.com/VikingOwl91/mistral-go-sdk/chat"
// Request represents a text moderation request (/v1/moderations).
type Request struct {

View File

@@ -3,8 +3,8 @@ package mistral
import (
"context"
"somegit.dev/vikingowl/mistral-go-sdk/classification"
"somegit.dev/vikingowl/mistral-go-sdk/moderation"
"github.com/VikingOwl91/mistral-go-sdk/classification"
"github.com/VikingOwl91/mistral-go-sdk/moderation"
)
// Moderate sends a text moderation request.

View File

@@ -7,8 +7,8 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/classification"
"somegit.dev/vikingowl/mistral-go-sdk/moderation"
"github.com/VikingOwl91/mistral-go-sdk/classification"
"github.com/VikingOwl91/mistral-go-sdk/moderation"
)
func TestModerate_Success(t *testing.T) {

84
observability/field.go Normal file
View File

@@ -0,0 +1,84 @@
package observability
// FieldType identifies the data type of a chat-completion-event field.
type FieldType string
const (
FieldTypeEnum FieldType = "ENUM"
FieldTypeText FieldType = "TEXT"
FieldTypeInt FieldType = "INT"
FieldTypeFloat FieldType = "FLOAT"
FieldTypeBool FieldType = "BOOL"
FieldTypeTimestamp FieldType = "TIMESTAMP"
FieldTypeArray FieldType = "ARRAY"
)
// FieldOperator is a filter operator supported on observability fields.
type FieldOperator string
const (
FieldOperatorLT FieldOperator = "lt"
FieldOperatorLTE FieldOperator = "lte"
FieldOperatorGT FieldOperator = "gt"
FieldOperatorGTE FieldOperator = "gte"
FieldOperatorStartsWith FieldOperator = "startswith"
FieldOperatorIStartsWith FieldOperator = "istartswith"
FieldOperatorEndsWith FieldOperator = "endswith"
FieldOperatorIEndsWith FieldOperator = "iendswith"
FieldOperatorContains FieldOperator = "contains"
FieldOperatorIContains FieldOperator = "icontains"
FieldOperatorMatches FieldOperator = "matches"
FieldOperatorNotContains FieldOperator = "notcontains"
FieldOperatorINotContain FieldOperator = "inotcontains"
FieldOperatorEq FieldOperator = "eq"
FieldOperatorNeq FieldOperator = "neq"
FieldOperatorIsNull FieldOperator = "isnull"
FieldOperatorIncludes FieldOperator = "includes"
FieldOperatorExcludes FieldOperator = "excludes"
FieldOperatorLenEq FieldOperator = "len_eq"
)
// BaseFieldDefinition describes a searchable chat-completion-event field.
type BaseFieldDefinition struct {
Name string `json:"name"`
Label string `json:"label"`
Type FieldType `json:"type"`
Group *string `json:"group,omitempty"`
SupportedOperators []FieldOperator `json:"supported_operators"`
}
// FieldGroup groups related field definitions for UI display.
type FieldGroup struct {
Name string `json:"name"`
Label string `json:"label"`
}
// ChatCompletionFields is the response of GET /v1/observability/chat-completion-fields.
type ChatCompletionFields struct {
FieldDefinitions []BaseFieldDefinition `json:"field_definitions"`
FieldGroups []FieldGroup `json:"field_groups"`
}
// ChatCompletionFieldOptions is the response of
// GET /v1/observability/chat-completion-fields/{field_name}/options.
//
// Each option may be a string, bool, or null — preserved as raw any.
type ChatCompletionFieldOptions struct {
Options []any `json:"options"`
}
// FieldOptionCountsRequest is the body of POST options-counts.
type FieldOptionCountsRequest struct {
FilterParams *FilterPayload `json:"filter_params,omitempty"`
}
// FieldOptionCountItem pairs a field value with how many events have it.
type FieldOptionCountItem struct {
Value string `json:"value"`
Count int `json:"count"`
}
// FieldOptionCounts is the response of POST options-counts.
type FieldOptionCounts struct {
Counts []FieldOptionCountItem `json:"counts"`
}

View File

@@ -6,7 +6,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/observability"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
// CreateCampaign creates a new observability campaign.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/observability"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
func TestCreateCampaign_Success(t *testing.T) {

View File

@@ -7,7 +7,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/observability"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
// CreateDataset creates a new observability dataset.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/observability"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
func datasetJSON() map[string]any {

View File

@@ -7,7 +7,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/observability"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
// SearchChatCompletionEvents searches for chat completion events.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/observability"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
func TestSearchChatCompletionEvents_Success(t *testing.T) {

46
observability_fields.go Normal file
View File

@@ -0,0 +1,46 @@
package mistral
import (
"context"
"fmt"
"net/url"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
// GetChatCompletionFields returns the searchable field definitions and groups
// for chat-completion observability events.
func (c *Client) GetChatCompletionFields(ctx context.Context) (*observability.ChatCompletionFields, error) {
var resp observability.ChatCompletionFields
if err := c.doJSON(ctx, "GET", "/v1/observability/chat-completion-fields", nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetChatCompletionFieldOptions returns the distinct values seen for the given
// field, filtered by the requested operator.
func (c *Client) GetChatCompletionFieldOptions(ctx context.Context, fieldName string, operator observability.FieldOperator) (*observability.ChatCompletionFieldOptions, error) {
q := url.Values{}
q.Set("operator", string(operator))
path := fmt.Sprintf("/v1/observability/chat-completion-fields/%s/options?%s", url.PathEscape(fieldName), q.Encode())
var resp observability.ChatCompletionFieldOptions
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetChatCompletionFieldOptionsCounts returns per-value event counts for the
// given field, optionally filtered by the supplied filter payload.
func (c *Client) GetChatCompletionFieldOptionsCounts(ctx context.Context, fieldName string, req *observability.FieldOptionCountsRequest) (*observability.FieldOptionCounts, error) {
if req == nil {
req = &observability.FieldOptionCountsRequest{}
}
path := fmt.Sprintf("/v1/observability/chat-completion-fields/%s/options-counts", url.PathEscape(fieldName))
var resp observability.FieldOptionCounts
if err := c.doJSON(ctx, "POST", path, req, &resp); err != nil {
return nil, err
}
return &resp, nil
}

View File

@@ -0,0 +1,100 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
func TestGetChatCompletionFields_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/v1/observability/chat-completion-fields" {
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"field_definitions": []map[string]any{
{
"name": "model",
"label": "Model",
"type": "ENUM",
"supported_operators": []string{"eq", "neq", "includes"},
},
},
"field_groups": []map[string]any{
{"name": "request", "label": "Request"},
},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.GetChatCompletionFields(context.Background())
if err != nil {
t.Fatal(err)
}
if len(resp.FieldDefinitions) != 1 {
t.Fatalf("got %d field definitions", len(resp.FieldDefinitions))
}
def := resp.FieldDefinitions[0]
if def.Name != "model" || def.Type != observability.FieldTypeEnum {
t.Errorf("unexpected field def: %+v", def)
}
if len(def.SupportedOperators) != 3 {
t.Errorf("got %d operators", len(def.SupportedOperators))
}
if len(resp.FieldGroups) != 1 || resp.FieldGroups[0].Name != "request" {
t.Errorf("unexpected groups: %+v", resp.FieldGroups)
}
}
func TestGetChatCompletionFieldOptions_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/observability/chat-completion-fields/model/options" {
t.Errorf("got path %s", r.URL.Path)
}
if got := r.URL.Query().Get("operator"); got != "eq" {
t.Errorf("got operator=%q want eq", got)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"options": []any{"mistral-small-latest", "mistral-large-latest", nil, true},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.GetChatCompletionFieldOptions(context.Background(), "model", observability.FieldOperatorEq)
if err != nil {
t.Fatal(err)
}
if len(resp.Options) != 4 {
t.Fatalf("got %d options", len(resp.Options))
}
}
func TestGetChatCompletionFieldOptionsCounts_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" || r.URL.Path != "/v1/observability/chat-completion-fields/model/options-counts" {
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"counts": []map[string]any{
{"value": "mistral-small-latest", "count": 42},
{"value": "mistral-large-latest", "count": 17},
},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.GetChatCompletionFieldOptionsCounts(context.Background(), "model", &observability.FieldOptionCountsRequest{})
if err != nil {
t.Fatal(err)
}
if len(resp.Counts) != 2 || resp.Counts[0].Count != 42 {
t.Errorf("unexpected counts: %+v", resp.Counts)
}
}

View File

@@ -7,7 +7,7 @@ import (
"net/url"
"strconv"
"somegit.dev/vikingowl/mistral-go-sdk/observability"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
// CreateJudge creates a new observability judge.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/observability"
"github.com/VikingOwl91/mistral-go-sdk/observability"
)
func judgeJSON() map[string]any {

View File

@@ -3,7 +3,7 @@ package mistral
import (
"context"
"somegit.dev/vikingowl/mistral-go-sdk/ocr"
"github.com/VikingOwl91/mistral-go-sdk/ocr"
)
// OCR performs optical character recognition on a document.

View File

@@ -7,7 +7,7 @@ import (
"net/http/httptest"
"testing"
"somegit.dev/vikingowl/mistral-go-sdk/ocr"
"github.com/VikingOwl91/mistral-go-sdk/ocr"
)
func TestOCR_Success(t *testing.T) {

View File

@@ -9,7 +9,7 @@ import (
"testing"
"time"
"somegit.dev/vikingowl/mistral-go-sdk/chat"
"github.com/VikingOwl91/mistral-go-sdk/chat"
)
func TestRetry_429ThenSuccess(t *testing.T) {

61
workflow/connectors.go Normal file
View File

@@ -0,0 +1,61 @@
package workflow
// ConnectorSlot declares a connector dependency for a workflow execution.
//
// Pass a slice of slots to BuildConnectorExtensions to produce the
// nested map expected on ExecutionRequest.Extensions.
type ConnectorSlot struct {
ConnectorName string `json:"connector_name"`
CredentialsName *string `json:"credentials_name,omitempty"`
}
// ConnectorBindings is the bindings list inside ConnectorExtensions.
type ConnectorBindings struct {
Bindings []ConnectorSlot `json:"bindings"`
}
// ConnectorExtensions is the value of the "mistralai" key in workflow extensions.
type ConnectorExtensions struct {
Connectors ConnectorBindings `json:"connectors"`
}
// WorkflowExtensions is the top-level shape of the extensions field
// expected by the workflow execute endpoint when binding connectors.
type WorkflowExtensions struct {
Mistralai ConnectorExtensions `json:"mistralai"`
}
// BuildConnectorExtensions returns the value to set on
// ExecutionRequest.Extensions for the given connector slots.
//
// The result is a map[string]any so callers can merge in additional
// extension keys without colliding with the connector wire shape.
func BuildConnectorExtensions(slots ...ConnectorSlot) map[string]any {
return map[string]any{
"mistralai": ConnectorExtensions{
Connectors: ConnectorBindings{Bindings: slots},
},
}
}
// ConnectorAuthStatus is the state of an OAuth flow emitted by a
// connector-auth custom task event.
type ConnectorAuthStatus string
const (
ConnectorAuthWaitingForAuth ConnectorAuthStatus = "waiting_for_auth"
ConnectorAuthConnected ConnectorAuthStatus = "connected"
ConnectorAuthAccessDenied ConnectorAuthStatus = "access_denied"
ConnectorAuthTimedOut ConnectorAuthStatus = "timed_out"
ConnectorAuthError ConnectorAuthStatus = "error"
)
// ConnectorAuthTaskState is the payload of a custom task event of type
// "connector-auth", emitted while a workflow waits for OAuth completion.
type ConnectorAuthTaskState struct {
ConnectorName string `json:"connector_name"`
ConnectorID string `json:"connector_id"`
Status ConnectorAuthStatus `json:"status"`
AuthURL *string `json:"auth_url,omitempty"`
Message *string `json:"message,omitempty"`
}

36
workflow/definition.go Normal file
View File

@@ -0,0 +1,36 @@
package workflow
// CodeDefinition describes a workflow's code-level interface: its input/output
// schemas, signal/query/update handlers, and execution constraints.
type CodeDefinition struct {
InputSchema map[string]any `json:"input_schema"`
OutputSchema map[string]any `json:"output_schema,omitempty"`
Signals []SignalDefinition `json:"signals,omitempty"`
Queries []QueryDefinition `json:"queries,omitempty"`
Updates []UpdateDefinition `json:"updates,omitempty"`
EnforceDeterminism bool `json:"enforce_determinism,omitempty"`
ExecutionTimeout *float64 `json:"execution_timeout,omitempty"`
}
// SignalDefinition describes a signal handler on a workflow.
type SignalDefinition struct {
Name string `json:"name"`
InputSchema map[string]any `json:"input_schema"`
Description *string `json:"description,omitempty"`
}
// QueryDefinition describes a query handler on a workflow.
type QueryDefinition struct {
Name string `json:"name"`
InputSchema map[string]any `json:"input_schema"`
Description *string `json:"description,omitempty"`
OutputSchema map[string]any `json:"output_schema,omitempty"`
}
// UpdateDefinition describes an update handler on a workflow.
type UpdateDefinition struct {
Name string `json:"name"`
InputSchema map[string]any `json:"input_schema"`
Description *string `json:"description,omitempty"`
OutputSchema map[string]any `json:"output_schema,omitempty"`
}

166
workflow/definition_test.go Normal file
View File

@@ -0,0 +1,166 @@
package workflow
import (
"encoding/json"
"testing"
)
func TestCodeDefinition_RoundTrip(t *testing.T) {
raw := `{
"input_schema": {"type": "object", "properties": {"prompt": {"type": "string"}}},
"output_schema": {"type": "object", "properties": {"result": {"type": "string"}}},
"signals": [
{"name": "cancel", "input_schema": {"type": "object"}, "description": "Cancel the workflow"}
],
"queries": [
{"name": "status", "input_schema": {"type": "object"}, "description": "Get status", "output_schema": {"type": "string"}}
],
"updates": [
{"name": "set_priority", "input_schema": {"type": "object", "properties": {"level": {"type": "integer"}}}, "description": "Set priority", "output_schema": null}
],
"enforce_determinism": true,
"execution_timeout": 3600.5
}`
var def CodeDefinition
if err := json.Unmarshal([]byte(raw), &def); err != nil {
t.Fatal(err)
}
if def.InputSchema == nil {
t.Fatal("InputSchema is nil")
}
if def.OutputSchema == nil {
t.Fatal("OutputSchema is nil")
}
if len(def.Signals) != 1 {
t.Fatalf("expected 1 signal, got %d", len(def.Signals))
}
if def.Signals[0].Name != "cancel" {
t.Errorf("signal name = %q, want cancel", def.Signals[0].Name)
}
if def.Signals[0].Description == nil || *def.Signals[0].Description != "Cancel the workflow" {
t.Errorf("signal description wrong")
}
if len(def.Queries) != 1 {
t.Fatalf("expected 1 query, got %d", len(def.Queries))
}
if def.Queries[0].Name != "status" {
t.Errorf("query name = %q, want status", def.Queries[0].Name)
}
if def.Queries[0].OutputSchema == nil {
t.Error("query OutputSchema is nil, expected non-nil")
}
if len(def.Updates) != 1 {
t.Fatalf("expected 1 update, got %d", len(def.Updates))
}
if def.Updates[0].Name != "set_priority" {
t.Errorf("update name = %q, want set_priority", def.Updates[0].Name)
}
if def.EnforceDeterminism != true {
t.Error("EnforceDeterminism should be true")
}
if def.ExecutionTimeout == nil || *def.ExecutionTimeout != 3600.5 {
t.Errorf("ExecutionTimeout = %v, want 3600.5", def.ExecutionTimeout)
}
// Re-marshal and verify round-trip
out, err := json.Marshal(def)
if err != nil {
t.Fatal(err)
}
var def2 CodeDefinition
if err := json.Unmarshal(out, &def2); err != nil {
t.Fatal(err)
}
if len(def2.Signals) != 1 || def2.Signals[0].Name != "cancel" {
t.Error("round-trip failed for signals")
}
if def2.EnforceDeterminism != true {
t.Error("round-trip failed for enforce_determinism")
}
}
func TestCodeDefinition_MinimalFields(t *testing.T) {
raw := `{"input_schema": {"type": "object"}}`
var def CodeDefinition
if err := json.Unmarshal([]byte(raw), &def); err != nil {
t.Fatal(err)
}
if def.InputSchema == nil {
t.Fatal("InputSchema is nil")
}
if def.OutputSchema != nil {
t.Errorf("OutputSchema should be nil, got %v", def.OutputSchema)
}
if def.Signals != nil {
t.Errorf("Signals should be nil, got %v", def.Signals)
}
if def.EnforceDeterminism != false {
t.Error("EnforceDeterminism should default to false")
}
if def.ExecutionTimeout != nil {
t.Errorf("ExecutionTimeout should be nil, got %v", def.ExecutionTimeout)
}
}
func TestRegistration_NewFields(t *testing.T) {
raw := `{
"id": "reg-1",
"workflow_id": "wf-1",
"task_queue": "legacy-queue",
"deployment_id": "dep-abc",
"compatible_with_chat_assistant": true,
"definition": {
"input_schema": {"type": "object"},
"enforce_determinism": false
},
"created_at": "2026-04-01T00:00:00Z",
"updated_at": "2026-04-02T00:00:00Z"
}`
var reg Registration
if err := json.Unmarshal([]byte(raw), &reg); err != nil {
t.Fatal(err)
}
if reg.ID != "reg-1" {
t.Errorf("ID = %q", reg.ID)
}
if reg.DeploymentID == nil || *reg.DeploymentID != "dep-abc" {
t.Errorf("DeploymentID = %v, want dep-abc", reg.DeploymentID)
}
if reg.CompatibleWithChatAssistant != true {
t.Error("CompatibleWithChatAssistant should be true")
}
if reg.Definition == nil {
t.Fatal("Definition is nil")
}
if reg.Definition.InputSchema == nil {
t.Error("Definition.InputSchema is nil")
}
// TaskQueue still works for backward compat
if reg.TaskQueue != "legacy-queue" {
t.Errorf("TaskQueue = %q, want legacy-queue", reg.TaskQueue)
}
}
func TestRegistration_NullDeploymentID(t *testing.T) {
raw := `{
"id": "reg-2",
"workflow_id": "wf-2",
"task_queue": "q",
"definition": {"input_schema": {"type": "object"}}
}`
var reg Registration
if err := json.Unmarshal([]byte(raw), &reg); err != nil {
t.Fatal(err)
}
if reg.DeploymentID != nil {
t.Errorf("DeploymentID should be nil, got %v", reg.DeploymentID)
}
if reg.CompatibleWithChatAssistant != false {
t.Error("CompatibleWithChatAssistant should default to false")
}
}

21
workflow/deployment.go Normal file
View File

@@ -0,0 +1,21 @@
package workflow
// Deployment represents a workflow deployment.
type Deployment struct {
ID string `json:"id"`
Name string `json:"name"`
IsActive bool `json:"is_active"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// DeploymentListResponse is the response from listing deployments.
type DeploymentListResponse struct {
Deployments []Deployment `json:"deployments"`
}
// DeploymentListParams holds query parameters for listing deployments.
type DeploymentListParams struct {
ActiveOnly *bool
WorkflowName *string
}

5
workflow/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package workflow provides types for the Mistral workflows API.
//
// Workflows support orchestrating multi-step processes with execution
// management, scheduling, event streaming, and observability.
package workflow

369
workflow/event.go Normal file
View File

@@ -0,0 +1,369 @@
package workflow
import (
"encoding/json"
"fmt"
)
// EventType identifies the kind of workflow event.
type EventType string
const (
EventWorkflowStarted EventType = "WORKFLOW_EXECUTION_STARTED"
EventWorkflowCompleted EventType = "WORKFLOW_EXECUTION_COMPLETED"
EventWorkflowFailed EventType = "WORKFLOW_EXECUTION_FAILED"
EventWorkflowCanceled EventType = "WORKFLOW_EXECUTION_CANCELED"
EventWorkflowContinuedAsNew EventType = "WORKFLOW_EXECUTION_CONTINUED_AS_NEW"
EventWorkflowTaskTimedOut EventType = "WORKFLOW_TASK_TIMED_OUT"
EventWorkflowTaskFailed EventType = "WORKFLOW_TASK_FAILED"
EventCustomTaskStarted EventType = "CUSTOM_TASK_STARTED"
EventCustomTaskInProgress EventType = "CUSTOM_TASK_IN_PROGRESS"
EventCustomTaskCompleted EventType = "CUSTOM_TASK_COMPLETED"
EventCustomTaskFailed EventType = "CUSTOM_TASK_FAILED"
EventCustomTaskTimedOut EventType = "CUSTOM_TASK_TIMED_OUT"
EventCustomTaskCanceled EventType = "CUSTOM_TASK_CANCELED"
EventActivityTaskStarted EventType = "ACTIVITY_TASK_STARTED"
EventActivityTaskCompleted EventType = "ACTIVITY_TASK_COMPLETED"
EventActivityTaskRetrying EventType = "ACTIVITY_TASK_RETRYING"
EventActivityTaskFailed EventType = "ACTIVITY_TASK_FAILED"
)
// EventSource identifies where an event originated.
type EventSource string
const (
EventSourceDatabase EventSource = "DATABASE"
EventSourceLive EventSource = "LIVE"
)
// Scope identifies the scope of an event subscription.
type Scope string
const (
ScopeActivity Scope = "activity"
ScopeWorkflow Scope = "workflow"
ScopeAll Scope = "*"
)
// Event is a sealed interface for workflow execution events.
type Event interface {
workflowEvent()
EventType() EventType
}
// eventBase holds fields common to all workflow events.
type eventBase struct {
ID string `json:"event_id"`
Timestamp int64 `json:"event_timestamp"`
RootWorkflowExecID string `json:"root_workflow_exec_id"`
ParentWorkflowExecID *string `json:"parent_workflow_exec_id"`
WorkflowExecID string `json:"workflow_exec_id"`
WorkflowRunID string `json:"workflow_run_id"`
WorkflowName string `json:"workflow_name"`
}
// WorkflowStartedAttributes holds typed attributes for workflow started events.
type WorkflowStartedAttributes struct {
TaskID string `json:"task_id"`
}
// JSONPayload holds a typed JSON value.
type JSONPayload struct {
Value any `json:"value"`
Type string `json:"type"`
}
// WorkflowCompletedAttributes holds typed attributes for workflow completed events.
type WorkflowCompletedAttributes struct {
TaskID string `json:"task_id"`
Result JSONPayload `json:"result"`
}
// WorkflowFailedAttributes holds typed attributes for workflow failed events.
type WorkflowFailedAttributes struct {
TaskID string `json:"task_id"`
Failure any `json:"failure"`
}
// WorkflowExecutionStartedEvent signals that a workflow execution has started.
type WorkflowExecutionStartedEvent struct {
eventBase
Attributes WorkflowStartedAttributes `json:"attributes"`
}
func (*WorkflowExecutionStartedEvent) workflowEvent() {}
func (*WorkflowExecutionStartedEvent) EventType() EventType { return EventWorkflowStarted }
// WorkflowExecutionCompletedEvent signals that a workflow execution has completed.
type WorkflowExecutionCompletedEvent struct {
eventBase
Attributes WorkflowCompletedAttributes `json:"attributes"`
}
func (*WorkflowExecutionCompletedEvent) workflowEvent() {}
func (*WorkflowExecutionCompletedEvent) EventType() EventType { return EventWorkflowCompleted }
// WorkflowExecutionFailedEvent signals that a workflow execution has failed.
type WorkflowExecutionFailedEvent struct {
eventBase
Attributes WorkflowFailedAttributes `json:"attributes"`
}
func (*WorkflowExecutionFailedEvent) workflowEvent() {}
func (*WorkflowExecutionFailedEvent) EventType() EventType { return EventWorkflowFailed }
// WorkflowExecutionCanceledEvent signals that a workflow execution was canceled.
type WorkflowExecutionCanceledEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*WorkflowExecutionCanceledEvent) workflowEvent() {}
func (*WorkflowExecutionCanceledEvent) EventType() EventType { return EventWorkflowCanceled }
// WorkflowExecutionContinuedAsNewEvent signals that a workflow continued as a new execution.
type WorkflowExecutionContinuedAsNewEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*WorkflowExecutionContinuedAsNewEvent) workflowEvent() {}
func (*WorkflowExecutionContinuedAsNewEvent) EventType() EventType { return EventWorkflowContinuedAsNew }
// WorkflowTaskTimedOutEvent signals that a workflow task timed out.
type WorkflowTaskTimedOutEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*WorkflowTaskTimedOutEvent) workflowEvent() {}
func (*WorkflowTaskTimedOutEvent) EventType() EventType { return EventWorkflowTaskTimedOut }
// WorkflowTaskFailedEvent signals that a workflow task failed.
type WorkflowTaskFailedEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*WorkflowTaskFailedEvent) workflowEvent() {}
func (*WorkflowTaskFailedEvent) EventType() EventType { return EventWorkflowTaskFailed }
// CustomTaskStartedEvent signals that a custom task has started.
type CustomTaskStartedEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*CustomTaskStartedEvent) workflowEvent() {}
func (*CustomTaskStartedEvent) EventType() EventType { return EventCustomTaskStarted }
// CustomTaskInProgressEvent signals that a custom task is in progress.
type CustomTaskInProgressEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*CustomTaskInProgressEvent) workflowEvent() {}
func (*CustomTaskInProgressEvent) EventType() EventType { return EventCustomTaskInProgress }
// CustomTaskCompletedEvent signals that a custom task has completed.
type CustomTaskCompletedEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*CustomTaskCompletedEvent) workflowEvent() {}
func (*CustomTaskCompletedEvent) EventType() EventType { return EventCustomTaskCompleted }
// CustomTaskFailedEvent signals that a custom task has failed.
type CustomTaskFailedEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*CustomTaskFailedEvent) workflowEvent() {}
func (*CustomTaskFailedEvent) EventType() EventType { return EventCustomTaskFailed }
// CustomTaskTimedOutEvent signals that a custom task timed out.
type CustomTaskTimedOutEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*CustomTaskTimedOutEvent) workflowEvent() {}
func (*CustomTaskTimedOutEvent) EventType() EventType { return EventCustomTaskTimedOut }
// CustomTaskCanceledEvent signals that a custom task was canceled.
type CustomTaskCanceledEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*CustomTaskCanceledEvent) workflowEvent() {}
func (*CustomTaskCanceledEvent) EventType() EventType { return EventCustomTaskCanceled }
// ActivityTaskStartedEvent signals that an activity task has started.
type ActivityTaskStartedEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*ActivityTaskStartedEvent) workflowEvent() {}
func (*ActivityTaskStartedEvent) EventType() EventType { return EventActivityTaskStarted }
// ActivityTaskCompletedEvent signals that an activity task has completed.
type ActivityTaskCompletedEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*ActivityTaskCompletedEvent) workflowEvent() {}
func (*ActivityTaskCompletedEvent) EventType() EventType { return EventActivityTaskCompleted }
// ActivityTaskRetryingEvent signals that an activity task is being retried.
type ActivityTaskRetryingEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*ActivityTaskRetryingEvent) workflowEvent() {}
func (*ActivityTaskRetryingEvent) EventType() EventType { return EventActivityTaskRetrying }
// ActivityTaskFailedEvent signals that an activity task has failed.
type ActivityTaskFailedEvent struct {
eventBase
Attributes json.RawMessage `json:"attributes"`
}
func (*ActivityTaskFailedEvent) workflowEvent() {}
func (*ActivityTaskFailedEvent) EventType() EventType { return EventActivityTaskFailed }
// UnknownEvent holds an event with an unrecognized event_type.
// This prevents the SDK from breaking when new event types are added.
type UnknownEvent struct {
eventBase
RawType string
Raw json.RawMessage
}
func (*UnknownEvent) workflowEvent() {}
func (e *UnknownEvent) EventType() EventType { return EventType(e.RawType) }
// UnmarshalEvent dispatches JSON to the concrete Event type
// based on the "event_type" discriminator field.
func UnmarshalEvent(data []byte) (Event, error) {
var probe struct {
Type string `json:"event_type"`
}
if err := json.Unmarshal(data, &probe); err != nil {
return nil, fmt.Errorf("mistral: unmarshal workflow event: %w", err)
}
switch probe.Type {
case string(EventWorkflowStarted):
var e WorkflowExecutionStartedEvent
return &e, json.Unmarshal(data, &e)
case string(EventWorkflowCompleted):
var e WorkflowExecutionCompletedEvent
return &e, json.Unmarshal(data, &e)
case string(EventWorkflowFailed):
var e WorkflowExecutionFailedEvent
return &e, json.Unmarshal(data, &e)
case string(EventWorkflowCanceled):
var e WorkflowExecutionCanceledEvent
return &e, json.Unmarshal(data, &e)
case string(EventWorkflowContinuedAsNew):
var e WorkflowExecutionContinuedAsNewEvent
return &e, json.Unmarshal(data, &e)
case string(EventWorkflowTaskTimedOut):
var e WorkflowTaskTimedOutEvent
return &e, json.Unmarshal(data, &e)
case string(EventWorkflowTaskFailed):
var e WorkflowTaskFailedEvent
return &e, json.Unmarshal(data, &e)
case string(EventCustomTaskStarted):
var e CustomTaskStartedEvent
return &e, json.Unmarshal(data, &e)
case string(EventCustomTaskInProgress):
var e CustomTaskInProgressEvent
return &e, json.Unmarshal(data, &e)
case string(EventCustomTaskCompleted):
var e CustomTaskCompletedEvent
return &e, json.Unmarshal(data, &e)
case string(EventCustomTaskFailed):
var e CustomTaskFailedEvent
return &e, json.Unmarshal(data, &e)
case string(EventCustomTaskTimedOut):
var e CustomTaskTimedOutEvent
return &e, json.Unmarshal(data, &e)
case string(EventCustomTaskCanceled):
var e CustomTaskCanceledEvent
return &e, json.Unmarshal(data, &e)
case string(EventActivityTaskStarted):
var e ActivityTaskStartedEvent
return &e, json.Unmarshal(data, &e)
case string(EventActivityTaskCompleted):
var e ActivityTaskCompletedEvent
return &e, json.Unmarshal(data, &e)
case string(EventActivityTaskRetrying):
var e ActivityTaskRetryingEvent
return &e, json.Unmarshal(data, &e)
case string(EventActivityTaskFailed):
var e ActivityTaskFailedEvent
return &e, json.Unmarshal(data, &e)
default:
var base eventBase
if err := json.Unmarshal(data, &base); err != nil {
return nil, fmt.Errorf("mistral: unmarshal workflow event base: %w", err)
}
return &UnknownEvent{
eventBase: base,
RawType: probe.Type,
Raw: json.RawMessage(data),
}, nil
}
}
// StreamPayload is a single SSE payload from the workflow event stream.
type StreamPayload struct {
Stream string `json:"stream"`
Data json.RawMessage `json:"data"`
WorkflowContext StreamWorkflowContext `json:"workflow_context"`
BrokerSequence int64 `json:"broker_sequence"`
}
// StreamWorkflowContext holds context for a workflow event stream.
type StreamWorkflowContext struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
}
// EventStreamParams holds query parameters for streaming workflow events.
type EventStreamParams struct {
Scope *Scope
ActivityName *string
ActivityID *string
WorkflowName *string
WorkflowExecID *string
RootWorkflowExecID *string
ParentWorkflowExecID *string
Stream *string
StartSeq *int
MetadataFilters map[string]any
WorkflowEventTypes []EventType
LastEventID *string
}
// EventListParams holds query parameters for listing workflow events.
type EventListParams struct {
RootWorkflowExecID *string
WorkflowExecID *string
WorkflowRunID *string
Limit *int
Cursor *string
}
// EventListResponse is the response from listing workflow events.
type EventListResponse struct {
Events []json.RawMessage `json:"events"`
NextCursor *string `json:"next_cursor,omitempty"`
}

158
workflow/event_test.go Normal file
View File

@@ -0,0 +1,158 @@
package workflow
import (
"encoding/json"
"fmt"
"testing"
)
func TestUnmarshalEvent_WorkflowExecutionCompleted(t *testing.T) {
data := []byte(`{
"event_id": "evt-1",
"event_timestamp": 1711929600000000000,
"root_workflow_exec_id": "root-1",
"parent_workflow_exec_id": null,
"workflow_exec_id": "exec-1",
"workflow_run_id": "run-1",
"workflow_name": "my-workflow",
"event_type": "WORKFLOW_EXECUTION_COMPLETED",
"attributes": {"task_id": "t1", "result": {"value": {"answer": 42}, "type": "json"}}
}`)
event, err := UnmarshalEvent(data)
if err != nil {
t.Fatal(err)
}
e, ok := event.(*WorkflowExecutionCompletedEvent)
if !ok {
t.Fatalf("expected *WorkflowExecutionCompletedEvent, got %T", event)
}
if e.ID != "evt-1" {
t.Errorf("got ID %q", e.ID)
}
if e.WorkflowName != "my-workflow" {
t.Errorf("got WorkflowName %q", e.WorkflowName)
}
if e.EventType() != EventWorkflowCompleted {
t.Errorf("got EventType %q", e.EventType())
}
}
func TestUnmarshalEvent_CustomTaskStarted(t *testing.T) {
data := []byte(`{
"event_id": "evt-2",
"event_timestamp": 1711929600000000000,
"root_workflow_exec_id": "root-1",
"parent_workflow_exec_id": "parent-1",
"workflow_exec_id": "exec-1",
"workflow_run_id": "run-1",
"workflow_name": "my-workflow",
"event_type": "CUSTOM_TASK_STARTED",
"attributes": {}
}`)
event, err := UnmarshalEvent(data)
if err != nil {
t.Fatal(err)
}
e, ok := event.(*CustomTaskStartedEvent)
if !ok {
t.Fatalf("expected *CustomTaskStartedEvent, got %T", event)
}
parent := "parent-1"
if e.ParentWorkflowExecID == nil || *e.ParentWorkflowExecID != parent {
t.Errorf("expected parent %q, got %v", parent, e.ParentWorkflowExecID)
}
}
func TestUnmarshalEvent_ActivityTaskRetrying(t *testing.T) {
data := []byte(`{
"event_id": "evt-3",
"event_timestamp": 1711929600000000000,
"root_workflow_exec_id": "root-1",
"parent_workflow_exec_id": null,
"workflow_exec_id": "exec-1",
"workflow_run_id": "run-1",
"workflow_name": "my-workflow",
"event_type": "ACTIVITY_TASK_RETRYING",
"attributes": {}
}`)
event, err := UnmarshalEvent(data)
if err != nil {
t.Fatal(err)
}
if _, ok := event.(*ActivityTaskRetryingEvent); !ok {
t.Fatalf("expected *ActivityTaskRetryingEvent, got %T", event)
}
}
func TestUnmarshalEvent_UnknownType(t *testing.T) {
data := []byte(`{
"event_id": "evt-4",
"event_timestamp": 1711929600000000000,
"root_workflow_exec_id": "root-1",
"parent_workflow_exec_id": null,
"workflow_exec_id": "exec-1",
"workflow_run_id": "run-1",
"workflow_name": "my-workflow",
"event_type": "FUTURE_EVENT_TYPE",
"attributes": {}
}`)
event, err := UnmarshalEvent(data)
if err != nil {
t.Fatal(err)
}
unk, ok := event.(*UnknownEvent)
if !ok {
t.Fatalf("expected *UnknownEvent, got %T", event)
}
if unk.RawType != "FUTURE_EVENT_TYPE" {
t.Errorf("got RawType %q", unk.RawType)
}
}
func TestUnmarshalEvent_AllTypes(t *testing.T) {
types := []struct {
eventType string
wantType string
}{
{"WORKFLOW_EXECUTION_STARTED", "*workflow.WorkflowExecutionStartedEvent"},
{"WORKFLOW_EXECUTION_COMPLETED", "*workflow.WorkflowExecutionCompletedEvent"},
{"WORKFLOW_EXECUTION_FAILED", "*workflow.WorkflowExecutionFailedEvent"},
{"WORKFLOW_EXECUTION_CANCELED", "*workflow.WorkflowExecutionCanceledEvent"},
{"WORKFLOW_EXECUTION_CONTINUED_AS_NEW", "*workflow.WorkflowExecutionContinuedAsNewEvent"},
{"WORKFLOW_TASK_TIMED_OUT", "*workflow.WorkflowTaskTimedOutEvent"},
{"WORKFLOW_TASK_FAILED", "*workflow.WorkflowTaskFailedEvent"},
{"CUSTOM_TASK_STARTED", "*workflow.CustomTaskStartedEvent"},
{"CUSTOM_TASK_IN_PROGRESS", "*workflow.CustomTaskInProgressEvent"},
{"CUSTOM_TASK_COMPLETED", "*workflow.CustomTaskCompletedEvent"},
{"CUSTOM_TASK_FAILED", "*workflow.CustomTaskFailedEvent"},
{"CUSTOM_TASK_TIMED_OUT", "*workflow.CustomTaskTimedOutEvent"},
{"CUSTOM_TASK_CANCELED", "*workflow.CustomTaskCanceledEvent"},
{"ACTIVITY_TASK_STARTED", "*workflow.ActivityTaskStartedEvent"},
{"ACTIVITY_TASK_COMPLETED", "*workflow.ActivityTaskCompletedEvent"},
{"ACTIVITY_TASK_RETRYING", "*workflow.ActivityTaskRetryingEvent"},
{"ACTIVITY_TASK_FAILED", "*workflow.ActivityTaskFailedEvent"},
}
for _, tt := range types {
t.Run(tt.eventType, func(t *testing.T) {
data, _ := json.Marshal(map[string]any{
"event_id": "evt",
"event_timestamp": 1711929600000000000,
"root_workflow_exec_id": "root",
"parent_workflow_exec_id": nil,
"workflow_exec_id": "exec",
"workflow_run_id": "run",
"workflow_name": "wf",
"event_type": tt.eventType,
"attributes": map[string]any{},
})
event, err := UnmarshalEvent(data)
if err != nil {
t.Fatal(err)
}
got := fmt.Sprintf("%T", event)
if got != tt.wantType {
t.Errorf("event_type %q: got %s, want %s", tt.eventType, got, tt.wantType)
}
})
}
}

175
workflow/execution.go Normal file
View File

@@ -0,0 +1,175 @@
package workflow
import "encoding/json"
// ExecutionStatus is the status of a workflow execution.
type ExecutionStatus string
const (
ExecutionRunning ExecutionStatus = "RUNNING"
ExecutionCompleted ExecutionStatus = "COMPLETED"
ExecutionFailed ExecutionStatus = "FAILED"
ExecutionCanceled ExecutionStatus = "CANCELED"
ExecutionTerminated ExecutionStatus = "TERMINATED"
ExecutionContinuedAsNew ExecutionStatus = "CONTINUED_AS_NEW"
ExecutionTimedOut ExecutionStatus = "TIMED_OUT"
ExecutionRetryingAfterErr ExecutionStatus = "RETRYING_AFTER_ERROR"
)
// ExecutionRequest is the request body for executing a workflow.
type ExecutionRequest struct {
ExecutionID *string `json:"execution_id,omitempty"`
Input map[string]any `json:"input,omitempty"`
EncodedInput *NetworkEncodedInput `json:"encoded_input,omitempty"`
WaitForResult bool `json:"wait_for_result,omitempty"`
TimeoutSeconds *float64 `json:"timeout_seconds,omitempty"`
CustomTracingAttributes map[string]string `json:"custom_tracing_attributes,omitempty"`
DeploymentName *string `json:"deployment_name,omitempty"`
// Extensions carries plugin-specific data such as connector bindings.
// Use BuildConnectorExtensions to construct the standard connector shape.
Extensions map[string]any `json:"extensions,omitempty"`
}
// ExecutionResponse is the response from a workflow execution.
type ExecutionResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result,omitempty"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
}
// EncodedPayloadOption identifies how a workflow payload was encoded.
type EncodedPayloadOption string
const (
EncodedPayloadOffloaded EncodedPayloadOption = "offloaded"
EncodedPayloadEncrypted EncodedPayloadOption = "encrypted"
EncodedPayloadEncryptedPartial EncodedPayloadOption = "encrypted-partial"
)
// NetworkEncodedInput holds a base64-encoded payload for workflow input.
type NetworkEncodedInput struct {
B64Payload string `json:"b64payload"`
EncodingOptions []EncodedPayloadOption `json:"encoding_options,omitempty"`
Empty bool `json:"empty,omitempty"`
}
// SignalInvocationBody is the request body for signaling a workflow execution.
type SignalInvocationBody struct {
Name string `json:"name"`
Input any `json:"input"`
}
// SignalResponse is the response from signaling a workflow execution.
type SignalResponse struct {
Message string `json:"message"`
}
// QueryInvocationBody is the request body for querying a workflow execution.
type QueryInvocationBody struct {
Name string `json:"name"`
Input any `json:"input,omitempty"`
}
// QueryResponse is the response from querying a workflow execution.
type QueryResponse struct {
QueryName string `json:"query_name"`
Result any `json:"result"`
}
// UpdateInvocationBody is the request body for updating a workflow execution.
type UpdateInvocationBody struct {
Name string `json:"name"`
Input any `json:"input,omitempty"`
}
// UpdateResponse is the response from updating a workflow execution.
type UpdateResponse struct {
UpdateName string `json:"update_name"`
Result any `json:"result"`
}
// ResetInvocationBody is the request body for resetting a workflow execution.
type ResetInvocationBody struct {
EventID int `json:"event_id"`
Reason *string `json:"reason,omitempty"`
ExcludeSignals bool `json:"exclude_signals,omitempty"`
ExcludeUpdates bool `json:"exclude_updates,omitempty"`
}
// BatchExecutionBody is the request body for batch execution operations.
type BatchExecutionBody struct {
ExecutionIDs []string `json:"execution_ids"`
}
// BatchExecutionResponse is the response from batch execution operations.
type BatchExecutionResponse struct {
Results map[string]BatchExecutionResult `json:"results,omitempty"`
}
// BatchExecutionResult is the result of a single batch operation.
type BatchExecutionResult struct {
Status string `json:"status"`
Error *string `json:"error,omitempty"`
}
// StreamParams holds query parameters for streaming workflow executions.
type StreamParams struct {
EventSource *EventSource
LastEventID *string
}
// TraceOTelResponse is the response from the OTel trace endpoint.
type TraceOTelResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
DataSource string `json:"data_source"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
OTelTraceID *string `json:"otel_trace_id,omitempty"`
OTelTraceData any `json:"otel_trace_data,omitempty"`
}
// TraceSummaryResponse is the response from the trace summary endpoint.
type TraceSummaryResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
SpanTree any `json:"span_tree,omitempty"`
}
// TraceEventsResponse is the response from the trace events endpoint.
type TraceEventsResponse struct {
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
RootExecutionID string `json:"root_execution_id"`
Status *ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
Result any `json:"result"`
ParentExecutionID *string `json:"parent_execution_id,omitempty"`
TotalDurationMs *int `json:"total_duration_ms,omitempty"`
Events []json.RawMessage `json:"events,omitempty"`
}
// TraceEventsParams holds query parameters for the trace events endpoint.
type TraceEventsParams struct {
MergeSameIDEvents *bool
IncludeInternalEvents *bool
}

27
workflow/metrics.go Normal file
View File

@@ -0,0 +1,27 @@
package workflow
// Metrics holds workflow performance metrics.
type Metrics struct {
ExecutionCount ScalarMetric `json:"execution_count"`
SuccessCount ScalarMetric `json:"success_count"`
ErrorCount ScalarMetric `json:"error_count"`
AverageLatencyMs ScalarMetric `json:"average_latency_ms"`
LatencyOverTime TimeSeriesMetric `json:"latency_over_time"`
RetryRate ScalarMetric `json:"retry_rate"`
}
// ScalarMetric holds a single numeric metric value.
type ScalarMetric struct {
Value float64 `json:"value"`
}
// TimeSeriesMetric holds a time series of [timestamp, value] pairs.
type TimeSeriesMetric struct {
Value [][]float64 `json:"value"`
}
// MetricsParams holds query parameters for workflow metrics.
type MetricsParams struct {
StartTime *string
EndTime *string
}

41
workflow/registration.go Normal file
View File

@@ -0,0 +1,41 @@
package workflow
// Registration represents a workflow registration.
type Registration struct {
ID string `json:"id"`
WorkflowID string `json:"workflow_id"`
Definition *CodeDefinition `json:"definition,omitempty"`
DeploymentID *string `json:"deployment_id,omitempty"`
CompatibleWithChatAssistant bool `json:"compatible_with_chat_assistant,omitempty"`
// Deprecated: use DeploymentID instead. Will be removed in a future release.
TaskQueue string `json:"task_queue"`
Workflow *Workflow `json:"workflow,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// RegistrationListResponse is the response from listing workflow registrations.
type RegistrationListResponse struct {
Registrations []Registration `json:"registrations"`
NextCursor *string `json:"next_cursor,omitempty"`
}
// RegistrationListParams holds query parameters for listing registrations.
type RegistrationListParams struct {
WorkflowID *string
TaskQueue *string
ActiveOnly *bool
IncludeShared *bool
WorkflowSearch *string
Archived *bool
WithWorkflow *bool
AvailableInChatAssistant *bool
Limit *int
Cursor *string
}
// RegistrationGetParams holds query parameters for getting a registration.
type RegistrationGetParams struct {
WithWorkflow *bool
IncludeShared *bool
}

26
workflow/run.go Normal file
View File

@@ -0,0 +1,26 @@
package workflow
// Run represents a workflow run.
type Run struct {
ID string `json:"id"`
WorkflowName string `json:"workflow_name"`
ExecutionID string `json:"execution_id"`
Status ExecutionStatus `json:"status"`
StartTime string `json:"start_time"`
EndTime *string `json:"end_time,omitempty"`
}
// ListRunsResponse is the response from listing workflow runs.
type ListRunsResponse struct {
Runs []Run `json:"runs"`
NextPageToken *string `json:"next_page_token,omitempty"`
}
// RunListParams holds query parameters for listing workflow runs.
type RunListParams struct {
WorkflowIdentifier *string
Search *string
Status *string
PageSize *int
NextPageToken *string
}

75
workflow/schedule.go Normal file
View File

@@ -0,0 +1,75 @@
package workflow
// ScheduleRequest is the request body for scheduling a workflow.
type ScheduleRequest struct {
Schedule ScheduleDefinition `json:"schedule"`
WorkflowRegistrationID *string `json:"workflow_registration_id,omitempty"`
WorkflowIdentifier *string `json:"workflow_identifier,omitempty"`
ScheduleID *string `json:"schedule_id,omitempty"`
DeploymentName *string `json:"deployment_name,omitempty"`
}
// ScheduleDefinition describes when and how a workflow should be scheduled.
type ScheduleDefinition struct {
Input any `json:"input"`
Calendars []ScheduleCalendar `json:"calendars,omitempty"`
Intervals []ScheduleInterval `json:"intervals,omitempty"`
CronExpressions []string `json:"cron_expressions,omitempty"`
Skip []ScheduleCalendar `json:"skip,omitempty"`
StartAt *string `json:"start_at,omitempty"`
EndAt *string `json:"end_at,omitempty"`
Jitter *string `json:"jitter,omitempty"`
TimeZoneName *string `json:"time_zone_name,omitempty"`
Policy *SchedulePolicy `json:"policy,omitempty"`
}
// ScheduleCalendar defines calendar-based schedule entries.
type ScheduleCalendar struct {
Second []ScheduleRange `json:"second,omitempty"`
Minute []ScheduleRange `json:"minute,omitempty"`
Hour []ScheduleRange `json:"hour,omitempty"`
DayOfMonth []ScheduleRange `json:"day_of_month,omitempty"`
Month []ScheduleRange `json:"month,omitempty"`
Year []ScheduleRange `json:"year,omitempty"`
DayOfWeek []ScheduleRange `json:"day_of_week,omitempty"`
Comment *string `json:"comment,omitempty"`
}
// ScheduleRange defines a numeric range for calendar schedules.
type ScheduleRange struct {
Start int `json:"start"`
End int `json:"end,omitempty"`
Step int `json:"step,omitempty"`
}
// ScheduleInterval defines an interval-based schedule.
type ScheduleInterval struct {
Every string `json:"every"`
Offset *string `json:"offset,omitempty"`
}
// SchedulePolicy controls schedule overlap and failure behavior.
type SchedulePolicy struct {
CatchupWindowSeconds int `json:"catchup_window_seconds,omitempty"`
Overlap *int `json:"overlap,omitempty"`
PauseOnFailure bool `json:"pause_on_failure,omitempty"`
}
// ScheduleResponse is the response from creating a workflow schedule.
type ScheduleResponse struct {
ScheduleID string `json:"schedule_id"`
}
// ScheduleListResponse is the response from listing workflow schedules.
type ScheduleListResponse struct {
Schedules []Schedule `json:"schedules"`
}
// Schedule represents a workflow schedule.
type Schedule struct {
ScheduleID string `json:"schedule_id"`
Definition ScheduleDefinition `json:"definition"`
WorkflowName string `json:"workflow_name"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}

12
workflow/worker.go Normal file
View File

@@ -0,0 +1,12 @@
package workflow
// WorkerInfo describes the worker scheduler the SDK is connected to.
//
// Returned by GET /v1/workflows/workers/whoami. Useful when running custom
// workers that need to know which scheduler / namespace to connect to.
// For managed deployments, prefer Registration.DeploymentID.
type WorkerInfo struct {
SchedulerURL string `json:"scheduler_url"`
Namespace string `json:"namespace"`
TLS bool `json:"tls"`
}

44
workflow/workflow.go Normal file
View File

@@ -0,0 +1,44 @@
package workflow
// Workflow represents a workflow definition.
type Workflow struct {
ID string `json:"id"`
Name string `json:"name"`
DisplayName *string `json:"display_name,omitempty"`
Description *string `json:"description,omitempty"`
OwnerID string `json:"owner_id"`
WorkspaceID string `json:"workspace_id"`
AvailableInChatAssistant bool `json:"available_in_chat_assistant"`
Archived bool `json:"archived"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// WorkflowUpdateRequest is the request body for updating a workflow.
type WorkflowUpdateRequest struct {
DisplayName *string `json:"display_name,omitempty"`
Description *string `json:"description,omitempty"`
AvailableInChatAssistant *bool `json:"available_in_chat_assistant,omitempty"`
}
// WorkflowListResponse is the response from listing workflows.
type WorkflowListResponse struct {
Workflows []Workflow `json:"workflows"`
NextCursor *string `json:"next_cursor,omitempty"`
}
// WorkflowListParams holds query parameters for listing workflows.
type WorkflowListParams struct {
ActiveOnly *bool
IncludeShared *bool
AvailableInChatAssistant *bool
Archived *bool
Cursor *string
Limit *int
}
// WorkflowArchiveResponse is the response from archiving/unarchiving a workflow.
type WorkflowArchiveResponse struct {
ID string `json:"id"`
Archived bool `json:"archived"`
}

168
workflows.go Normal file
View File

@@ -0,0 +1,168 @@
package mistral
import (
"context"
"fmt"
"net/url"
"strconv"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// ListWorkflows lists workflows.
func (c *Client) ListWorkflows(ctx context.Context, params *workflow.WorkflowListParams) (*workflow.WorkflowListResponse, error) {
path := "/v1/workflows"
if params != nil {
q := url.Values{}
if params.ActiveOnly != nil {
q.Set("active_only", strconv.FormatBool(*params.ActiveOnly))
}
if params.IncludeShared != nil {
q.Set("include_shared", strconv.FormatBool(*params.IncludeShared))
}
if params.AvailableInChatAssistant != nil {
q.Set("available_in_chat_assistant", strconv.FormatBool(*params.AvailableInChatAssistant))
}
if params.Archived != nil {
q.Set("archived", strconv.FormatBool(*params.Archived))
}
if params.Cursor != nil {
q.Set("cursor", *params.Cursor)
}
if params.Limit != nil {
q.Set("limit", strconv.Itoa(*params.Limit))
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
var resp workflow.WorkflowListResponse
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflow retrieves a workflow by identifier.
func (c *Client) GetWorkflow(ctx context.Context, workflowIdentifier string) (*workflow.Workflow, error) {
var resp workflow.Workflow
if err := c.doJSON(ctx, "GET", fmt.Sprintf("/v1/workflows/%s", workflowIdentifier), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// UpdateWorkflow updates a workflow.
func (c *Client) UpdateWorkflow(ctx context.Context, workflowIdentifier string, req *workflow.WorkflowUpdateRequest) (*workflow.Workflow, error) {
var resp workflow.Workflow
if err := c.doJSON(ctx, "PUT", fmt.Sprintf("/v1/workflows/%s", workflowIdentifier), req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ArchiveWorkflow archives a workflow.
func (c *Client) ArchiveWorkflow(ctx context.Context, workflowIdentifier string) (*workflow.WorkflowArchiveResponse, error) {
var resp workflow.WorkflowArchiveResponse
if err := c.doJSON(ctx, "PUT", fmt.Sprintf("/v1/workflows/%s/archive", workflowIdentifier), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// UnarchiveWorkflow unarchives a workflow.
func (c *Client) UnarchiveWorkflow(ctx context.Context, workflowIdentifier string) (*workflow.WorkflowArchiveResponse, error) {
var resp workflow.WorkflowArchiveResponse
if err := c.doJSON(ctx, "PUT", fmt.Sprintf("/v1/workflows/%s/unarchive", workflowIdentifier), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ExecuteWorkflow executes a workflow.
func (c *Client) ExecuteWorkflow(ctx context.Context, workflowIdentifier string, req *workflow.ExecutionRequest) (*workflow.ExecutionResponse, error) {
var resp workflow.ExecutionResponse
if err := c.doJSON(ctx, "POST", fmt.Sprintf("/v1/workflows/%s/execute", workflowIdentifier), req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ListWorkflowRegistrations lists workflow registrations.
func (c *Client) ListWorkflowRegistrations(ctx context.Context, params *workflow.RegistrationListParams) (*workflow.RegistrationListResponse, error) {
path := "/v1/workflows/registrations"
if params != nil {
q := url.Values{}
if params.WorkflowID != nil {
q.Set("workflow_id", *params.WorkflowID)
}
if params.TaskQueue != nil {
q.Set("task_queue", *params.TaskQueue)
}
if params.ActiveOnly != nil {
q.Set("active_only", strconv.FormatBool(*params.ActiveOnly))
}
if params.IncludeShared != nil {
q.Set("include_shared", strconv.FormatBool(*params.IncludeShared))
}
if params.WorkflowSearch != nil {
q.Set("workflow_search", *params.WorkflowSearch)
}
if params.Archived != nil {
q.Set("archived", strconv.FormatBool(*params.Archived))
}
if params.WithWorkflow != nil {
q.Set("with_workflow", strconv.FormatBool(*params.WithWorkflow))
}
if params.AvailableInChatAssistant != nil {
q.Set("available_in_chat_assistant", strconv.FormatBool(*params.AvailableInChatAssistant))
}
if params.Limit != nil {
q.Set("limit", strconv.Itoa(*params.Limit))
}
if params.Cursor != nil {
q.Set("cursor", *params.Cursor)
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
var resp workflow.RegistrationListResponse
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflowRegistration retrieves a workflow registration by ID.
func (c *Client) GetWorkflowRegistration(ctx context.Context, registrationID string, params *workflow.RegistrationGetParams) (*workflow.Registration, error) {
path := fmt.Sprintf("/v1/workflows/registrations/%s", registrationID)
if params != nil {
q := url.Values{}
if params.WithWorkflow != nil {
q.Set("with_workflow", strconv.FormatBool(*params.WithWorkflow))
}
if params.IncludeShared != nil {
q.Set("include_shared", strconv.FormatBool(*params.IncludeShared))
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
var resp workflow.Registration
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ExecuteWorkflowRegistration executes a workflow via its registration.
//
// Deprecated: Use ExecuteWorkflow instead. This method will be removed in a future release.
func (c *Client) ExecuteWorkflowRegistration(ctx context.Context, registrationID string, req *workflow.ExecutionRequest) (*workflow.ExecutionResponse, error) {
var resp workflow.ExecutionResponse
if err := c.doJSON(ctx, "POST", fmt.Sprintf("/v1/workflows/registrations/%s/execute", registrationID), req, &resp); err != nil {
return nil, err
}
return &resp, nil
}

41
workflows_deployments.go Normal file
View File

@@ -0,0 +1,41 @@
package mistral
import (
"context"
"fmt"
"net/url"
"strconv"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// ListWorkflowDeployments lists workflow deployments.
func (c *Client) ListWorkflowDeployments(ctx context.Context, params *workflow.DeploymentListParams) (*workflow.DeploymentListResponse, error) {
path := "/v1/workflows/deployments"
if params != nil {
q := url.Values{}
if params.ActiveOnly != nil {
q.Set("active_only", strconv.FormatBool(*params.ActiveOnly))
}
if params.WorkflowName != nil {
q.Set("workflow_name", *params.WorkflowName)
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
var resp workflow.DeploymentListResponse
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflowDeployment retrieves a workflow deployment by ID.
func (c *Client) GetWorkflowDeployment(ctx context.Context, deploymentID string) (*workflow.Deployment, error) {
var resp workflow.Deployment
if err := c.doJSON(ctx, "GET", fmt.Sprintf("/v1/workflows/deployments/%s", deploymentID), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}

View File

@@ -0,0 +1,57 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestListWorkflowDeployments_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/deployments" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"deployments": []map[string]any{
{"id": "dep-1", "name": "prod", "is_active": true, "created_at": "2026-01-01", "updated_at": "2026-01-01"},
},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.ListWorkflowDeployments(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
if len(resp.Deployments) != 1 {
t.Fatalf("got %d deployments", len(resp.Deployments))
}
if resp.Deployments[0].Name != "prod" {
t.Errorf("got name %q", resp.Deployments[0].Name)
}
}
func TestGetWorkflowDeployment_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/deployments/dep-1" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"id": "dep-1", "name": "prod", "is_active": true,
"created_at": "2026-01-01", "updated_at": "2026-01-01",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
dep, err := client.GetWorkflowDeployment(context.Background(), "dep-1")
if err != nil {
t.Fatal(err)
}
if dep.ID != "dep-1" {
t.Errorf("got id %q", dep.ID)
}
}

99
workflows_events.go Normal file
View File

@@ -0,0 +1,99 @@
package mistral
import (
"context"
"encoding/json"
"net/url"
"strconv"
"strings"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// StreamWorkflowEvents streams workflow events via SSE.
func (c *Client) StreamWorkflowEvents(ctx context.Context, params *workflow.EventStreamParams) (*WorkflowEventStream, error) {
path := "/v1/workflows/events/stream"
if params != nil {
q := url.Values{}
if params.Scope != nil {
q.Set("scope", string(*params.Scope))
}
if params.ActivityName != nil {
q.Set("activity_name", *params.ActivityName)
}
if params.ActivityID != nil {
q.Set("activity_id", *params.ActivityID)
}
if params.WorkflowName != nil {
q.Set("workflow_name", *params.WorkflowName)
}
if params.WorkflowExecID != nil {
q.Set("workflow_exec_id", *params.WorkflowExecID)
}
if params.RootWorkflowExecID != nil {
q.Set("root_workflow_exec_id", *params.RootWorkflowExecID)
}
if params.ParentWorkflowExecID != nil {
q.Set("parent_workflow_exec_id", *params.ParentWorkflowExecID)
}
if params.Stream != nil {
q.Set("stream", *params.Stream)
}
if params.StartSeq != nil {
q.Set("start_seq", strconv.Itoa(*params.StartSeq))
}
if params.MetadataFilters != nil {
data, _ := json.Marshal(params.MetadataFilters)
q.Set("metadata_filters", string(data))
}
if len(params.WorkflowEventTypes) > 0 {
types := make([]string, len(params.WorkflowEventTypes))
for i, et := range params.WorkflowEventTypes {
types[i] = string(et)
}
q.Set("workflow_event_types", strings.Join(types, ","))
}
if params.LastEventID != nil {
q.Set("last_event_id", *params.LastEventID)
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
resp, err := c.doStream(ctx, "GET", path, nil)
if err != nil {
return nil, err
}
return newWorkflowEventStream(resp.Body), nil
}
// ListWorkflowEvents lists workflow events.
func (c *Client) ListWorkflowEvents(ctx context.Context, params *workflow.EventListParams) (*workflow.EventListResponse, error) {
path := "/v1/workflows/events/list"
if params != nil {
q := url.Values{}
if params.RootWorkflowExecID != nil {
q.Set("root_workflow_exec_id", *params.RootWorkflowExecID)
}
if params.WorkflowExecID != nil {
q.Set("workflow_exec_id", *params.WorkflowExecID)
}
if params.WorkflowRunID != nil {
q.Set("workflow_run_id", *params.WorkflowRunID)
}
if params.Limit != nil {
q.Set("limit", strconv.Itoa(*params.Limit))
}
if params.Cursor != nil {
q.Set("cursor", *params.Cursor)
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
var resp workflow.EventListResponse
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}

40
workflows_events_test.go Normal file
View File

@@ -0,0 +1,40 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
func TestListWorkflowEvents_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/events/list" {
t.Errorf("got path %s", r.URL.Path)
}
if r.URL.Query().Get("limit") != "50" {
t.Errorf("got limit %q", r.URL.Query().Get("limit"))
}
json.NewEncoder(w).Encode(map[string]any{
"events": []map[string]any{{"event_type": "WORKFLOW_EXECUTION_STARTED"}},
"next_cursor": "cur-1",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
limit := 50
resp, err := client.ListWorkflowEvents(context.Background(), &workflow.EventListParams{Limit: &limit})
if err != nil {
t.Fatal(err)
}
if len(resp.Events) != 1 {
t.Fatalf("got %d events", len(resp.Events))
}
if resp.NextCursor == nil || *resp.NextCursor != "cur-1" {
t.Errorf("got cursor %v", resp.NextCursor)
}
}

255
workflows_executions.go Normal file
View File

@@ -0,0 +1,255 @@
package mistral
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"time"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// GetWorkflowExecution retrieves a workflow execution by ID.
func (c *Client) GetWorkflowExecution(ctx context.Context, executionID string) (*workflow.ExecutionResponse, error) {
var resp workflow.ExecutionResponse
if err := c.doJSON(ctx, "GET", fmt.Sprintf("/v1/workflows/executions/%s", executionID), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflowExecutionHistory retrieves the history of a workflow execution.
func (c *Client) GetWorkflowExecutionHistory(ctx context.Context, executionID string, decodePayloads *bool) (json.RawMessage, error) {
path := fmt.Sprintf("/v1/workflows/executions/%s/history", executionID)
if decodePayloads != nil {
path += "?decode_payloads=" + strconv.FormatBool(*decodePayloads)
}
var resp json.RawMessage
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return resp, nil
}
// StreamWorkflowExecution streams events for a workflow execution via SSE.
func (c *Client) StreamWorkflowExecution(ctx context.Context, executionID string, params *workflow.StreamParams) (*WorkflowEventStream, error) {
path := fmt.Sprintf("/v1/workflows/executions/%s/stream", executionID)
if params != nil {
q := url.Values{}
if params.EventSource != nil {
q.Set("event_source", string(*params.EventSource))
}
if params.LastEventID != nil {
q.Set("last_event_id", *params.LastEventID)
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
resp, err := c.doStream(ctx, "GET", path, nil)
if err != nil {
return nil, err
}
return newWorkflowEventStream(resp.Body), nil
}
// SignalWorkflowExecution sends a signal to a workflow execution.
func (c *Client) SignalWorkflowExecution(ctx context.Context, executionID string, req *workflow.SignalInvocationBody) (*workflow.SignalResponse, error) {
var resp workflow.SignalResponse
if err := c.doJSON(ctx, "POST", fmt.Sprintf("/v1/workflows/executions/%s/signals", executionID), req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// QueryWorkflowExecution queries a workflow execution.
func (c *Client) QueryWorkflowExecution(ctx context.Context, executionID string, req *workflow.QueryInvocationBody) (*workflow.QueryResponse, error) {
var resp workflow.QueryResponse
if err := c.doJSON(ctx, "POST", fmt.Sprintf("/v1/workflows/executions/%s/queries", executionID), req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// UpdateWorkflowExecution sends an update to a workflow execution.
func (c *Client) UpdateWorkflowExecution(ctx context.Context, executionID string, req *workflow.UpdateInvocationBody) (*workflow.UpdateResponse, error) {
var resp workflow.UpdateResponse
if err := c.doJSON(ctx, "POST", fmt.Sprintf("/v1/workflows/executions/%s/updates", executionID), req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// TerminateWorkflowExecution terminates a workflow execution.
func (c *Client) TerminateWorkflowExecution(ctx context.Context, executionID string) error {
resp, err := c.do(ctx, "POST", fmt.Sprintf("/v1/workflows/executions/%s/terminate", executionID), nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return parseAPIError(resp)
}
return nil
}
// CancelWorkflowExecution cancels a workflow execution.
func (c *Client) CancelWorkflowExecution(ctx context.Context, executionID string) error {
resp, err := c.do(ctx, "POST", fmt.Sprintf("/v1/workflows/executions/%s/cancel", executionID), nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return parseAPIError(resp)
}
return nil
}
// ResetWorkflowExecution resets a workflow execution to a specific event.
func (c *Client) ResetWorkflowExecution(ctx context.Context, executionID string, req *workflow.ResetInvocationBody) error {
return c.doJSON(ctx, "POST", fmt.Sprintf("/v1/workflows/executions/%s/reset", executionID), req, nil)
}
// BatchCancelWorkflowExecutions cancels multiple workflow executions.
func (c *Client) BatchCancelWorkflowExecutions(ctx context.Context, req *workflow.BatchExecutionBody) (*workflow.BatchExecutionResponse, error) {
var resp workflow.BatchExecutionResponse
if err := c.doJSON(ctx, "POST", "/v1/workflows/executions/cancel", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// BatchTerminateWorkflowExecutions terminates multiple workflow executions.
func (c *Client) BatchTerminateWorkflowExecutions(ctx context.Context, req *workflow.BatchExecutionBody) (*workflow.BatchExecutionResponse, error) {
var resp workflow.BatchExecutionResponse
if err := c.doJSON(ctx, "POST", "/v1/workflows/executions/terminate", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflowExecutionTraceOTel retrieves the OpenTelemetry trace for a workflow execution.
func (c *Client) GetWorkflowExecutionTraceOTel(ctx context.Context, executionID string) (*workflow.TraceOTelResponse, error) {
var resp workflow.TraceOTelResponse
if err := c.doJSON(ctx, "GET", fmt.Sprintf("/v1/workflows/executions/%s/trace/otel", executionID), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflowExecutionTraceSummary retrieves the trace summary for a workflow execution.
func (c *Client) GetWorkflowExecutionTraceSummary(ctx context.Context, executionID string) (*workflow.TraceSummaryResponse, error) {
var resp workflow.TraceSummaryResponse
if err := c.doJSON(ctx, "GET", fmt.Sprintf("/v1/workflows/executions/%s/trace/summary", executionID), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflowExecutionTraceEvents retrieves the trace events for a workflow execution.
func (c *Client) GetWorkflowExecutionTraceEvents(ctx context.Context, executionID string, params *workflow.TraceEventsParams) (*workflow.TraceEventsResponse, error) {
path := fmt.Sprintf("/v1/workflows/executions/%s/trace/events", executionID)
if params != nil {
q := url.Values{}
if params.MergeSameIDEvents != nil {
q.Set("merge_same_id_events", strconv.FormatBool(*params.MergeSameIDEvents))
}
if params.IncludeInternalEvents != nil {
q.Set("include_internal_events", strconv.FormatBool(*params.IncludeInternalEvents))
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
var resp workflow.TraceEventsResponse
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// WorkflowEventStream wraps the generic Stream to provide typed workflow events
// with StreamPayload envelope metadata.
type WorkflowEventStream struct {
stream *Stream[json.RawMessage]
event workflow.Event
payload *workflow.StreamPayload
err error
}
func newWorkflowEventStream(body readCloser) *WorkflowEventStream {
return &WorkflowEventStream{
stream: newStream[json.RawMessage](body),
}
}
// Next advances to the next event. Returns false when done or on error.
func (s *WorkflowEventStream) Next() bool {
if s.err != nil {
return false
}
if !s.stream.Next() {
s.err = s.stream.Err()
return false
}
var payload workflow.StreamPayload
if err := json.Unmarshal(s.stream.Current(), &payload); err != nil {
s.err = fmt.Errorf("mistral: decode workflow stream payload: %w", err)
return false
}
event, err := workflow.UnmarshalEvent(payload.Data)
if err != nil {
s.err = err
return false
}
s.event = event
s.payload = &payload
return true
}
// Current returns the most recently read workflow event.
func (s *WorkflowEventStream) Current() workflow.Event { return s.event }
// CurrentPayload returns the full StreamPayload envelope of the current event.
func (s *WorkflowEventStream) CurrentPayload() *workflow.StreamPayload { return s.payload }
// Err returns any error encountered during streaming.
func (s *WorkflowEventStream) Err() error { return s.err }
// Close releases the underlying connection.
func (s *WorkflowEventStream) Close() error { return s.stream.Close() }
// ExecuteWorkflowAndWait executes a workflow and polls until completion.
func (c *Client) ExecuteWorkflowAndWait(ctx context.Context, workflowIdentifier string, req *workflow.ExecutionRequest) (*workflow.ExecutionResponse, error) {
execResp, err := c.ExecuteWorkflow(ctx, workflowIdentifier, req)
if err != nil {
return nil, err
}
for {
if isTerminal(execResp.Status) {
return execResp, nil
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(500 * time.Millisecond):
}
execResp, err = c.GetWorkflowExecution(ctx, execResp.ExecutionID)
if err != nil {
return nil, err
}
}
}
func isTerminal(s workflow.ExecutionStatus) bool {
switch s {
case workflow.ExecutionCompleted, workflow.ExecutionFailed,
workflow.ExecutionCanceled, workflow.ExecutionTerminated,
workflow.ExecutionTimedOut:
return true
}
return false
}

View File

@@ -0,0 +1,266 @@
package mistral
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
func TestGetWorkflowExecution_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/executions/exec-1" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"workflow_name": "my-flow", "execution_id": "exec-1",
"root_execution_id": "exec-1", "status": "COMPLETED",
"start_time": "2026-01-01T00:00:00Z",
"end_time": "2026-01-01T00:01:00Z",
"result": map[string]any{"answer": 42},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.GetWorkflowExecution(context.Background(), "exec-1")
if err != nil {
t.Fatal(err)
}
if resp.Status != workflow.ExecutionCompleted {
t.Errorf("got status %q", resp.Status)
}
}
func TestSignalWorkflowExecution_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows/executions/exec-1/signals" {
t.Errorf("got path %s", r.URL.Path)
}
var body map[string]any
json.NewDecoder(r.Body).Decode(&body)
if body["name"] != "approval" {
t.Errorf("got name %v", body["name"])
}
w.WriteHeader(http.StatusAccepted)
json.NewEncoder(w).Encode(map[string]any{"message": "Signal accepted"})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.SignalWorkflowExecution(context.Background(), "exec-1", &workflow.SignalInvocationBody{
Name: "approval",
Input: map[string]any{"approved": true},
})
if err != nil {
t.Fatal(err)
}
if resp.Message != "Signal accepted" {
t.Errorf("got message %q", resp.Message)
}
}
func TestTerminateWorkflowExecution_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows/executions/exec-1/terminate" {
t.Errorf("got path %s", r.URL.Path)
}
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
err := client.TerminateWorkflowExecution(context.Background(), "exec-1")
if err != nil {
t.Fatal(err)
}
}
func TestBatchCancelWorkflowExecutions_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows/executions/cancel" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"results": map[string]any{
"exec-1": map[string]any{"status": "success"},
"exec-2": map[string]any{"status": "failure", "error": "not found"},
},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.BatchCancelWorkflowExecutions(context.Background(), &workflow.BatchExecutionBody{
ExecutionIDs: []string{"exec-1", "exec-2"},
})
if err != nil {
t.Fatal(err)
}
if resp.Results["exec-1"].Status != "success" {
t.Errorf("got exec-1 status %q", resp.Results["exec-1"].Status)
}
if resp.Results["exec-2"].Error == nil || *resp.Results["exec-2"].Error != "not found" {
t.Errorf("expected exec-2 error")
}
}
func TestStreamWorkflowExecution_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows/executions/exec-1/stream" {
t.Errorf("got path %s", r.URL.Path)
}
w.Header().Set("Content-Type", "text/event-stream")
flusher, _ := w.(http.Flusher)
payloads := []map[string]any{
{
"stream": "events",
"data": map[string]any{
"event_id": "evt-1", "event_timestamp": 1711929600000000000,
"root_workflow_exec_id": "exec-1", "parent_workflow_exec_id": nil,
"workflow_exec_id": "exec-1", "workflow_run_id": "run-1",
"workflow_name": "my-flow", "event_type": "WORKFLOW_EXECUTION_STARTED",
"attributes": map[string]any{},
},
"workflow_context": map[string]any{
"namespace": "default", "workflow_name": "my-flow", "workflow_exec_id": "exec-1",
},
"broker_sequence": 1,
},
{
"stream": "events",
"data": map[string]any{
"event_id": "evt-2", "event_timestamp": 1711929601000000000,
"root_workflow_exec_id": "exec-1", "parent_workflow_exec_id": nil,
"workflow_exec_id": "exec-1", "workflow_run_id": "run-1",
"workflow_name": "my-flow", "event_type": "WORKFLOW_EXECUTION_COMPLETED",
"attributes": map[string]any{"result": map[string]any{"value": 42, "type": "json"}},
},
"workflow_context": map[string]any{
"namespace": "default", "workflow_name": "my-flow", "workflow_exec_id": "exec-1",
},
"broker_sequence": 2,
},
}
for _, p := range payloads {
data, _ := json.Marshal(p)
fmt.Fprintf(w, "data: %s\n\n", data)
flusher.Flush()
}
fmt.Fprint(w, "data: [DONE]\n\n")
flusher.Flush()
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
stream, err := client.StreamWorkflowExecution(context.Background(), "exec-1", nil)
if err != nil {
t.Fatal(err)
}
defer stream.Close()
var events []workflow.Event
var lastPayload *workflow.StreamPayload
for stream.Next() {
events = append(events, stream.Current())
lastPayload = stream.CurrentPayload()
}
if stream.Err() != nil {
t.Fatal(stream.Err())
}
if len(events) != 2 {
t.Fatalf("got %d events, want 2", len(events))
}
if _, ok := events[0].(*workflow.WorkflowExecutionStartedEvent); !ok {
t.Errorf("expected *WorkflowExecutionStartedEvent, got %T", events[0])
}
if _, ok := events[1].(*workflow.WorkflowExecutionCompletedEvent); !ok {
t.Errorf("expected *WorkflowExecutionCompletedEvent, got %T", events[1])
}
if lastPayload.WorkflowContext.WorkflowName != "my-flow" {
t.Errorf("got workflow context name %q", lastPayload.WorkflowContext.WorkflowName)
}
}
func TestGetWorkflowExecutionTraceOTel_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/executions/exec-1/trace/otel" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"workflow_name": "my-flow", "execution_id": "exec-1",
"root_execution_id": "exec-1", "status": "COMPLETED",
"start_time": "2026-01-01T00:00:00Z", "data_source": "temporal",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.GetWorkflowExecutionTraceOTel(context.Background(), "exec-1")
if err != nil {
t.Fatal(err)
}
if resp.DataSource != "temporal" {
t.Errorf("got data_source %q", resp.DataSource)
}
}
func TestExecuteWorkflowAndWait_Success(t *testing.T) {
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == "POST" && r.URL.Path == "/v1/workflows/wf-1/execute":
json.NewEncoder(w).Encode(map[string]any{
"workflow_name": "my-flow", "execution_id": "exec-1",
"root_execution_id": "exec-1", "status": "RUNNING",
"start_time": "2026-01-01T00:00:00Z",
})
case r.Method == "GET" && r.URL.Path == "/v1/workflows/executions/exec-1":
calls++
status := "RUNNING"
if calls >= 2 {
status = "COMPLETED"
}
resp := map[string]any{
"workflow_name": "my-flow", "execution_id": "exec-1",
"root_execution_id": "exec-1", "status": status,
"start_time": "2026-01-01T00:00:00Z",
}
if status == "COMPLETED" {
resp["result"] = map[string]any{"answer": 42}
}
json.NewEncoder(w).Encode(resp)
default:
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
}
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.ExecuteWorkflowAndWait(context.Background(), "wf-1", &workflow.ExecutionRequest{
Input: map[string]any{"prompt": "hello"},
})
if err != nil {
t.Fatal(err)
}
if resp.Status != workflow.ExecutionCompleted {
t.Errorf("got status %q", resp.Status)
}
}

View File

@@ -0,0 +1,106 @@
package mistral
import (
"encoding/json"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/conversation"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
func TestNetworkEncodedInput_EncodingOptions(t *testing.T) {
in := workflow.NetworkEncodedInput{
B64Payload: "eyJrIjoidiJ9",
EncodingOptions: []workflow.EncodedPayloadOption{workflow.EncodedPayloadOffloaded, workflow.EncodedPayloadEncrypted},
}
b, err := json.Marshal(in)
if err != nil {
t.Fatal(err)
}
var got map[string]any
if err := json.Unmarshal(b, &got); err != nil {
t.Fatal(err)
}
opts, ok := got["encoding_options"].([]any)
if !ok || len(opts) != 2 {
t.Fatalf("unexpected encoding_options: %v", got["encoding_options"])
}
if opts[0] != "offloaded" || opts[1] != "encrypted" {
t.Errorf("got %v, want [offloaded encrypted]", opts)
}
}
func TestBuildConnectorExtensions_WireShape(t *testing.T) {
creds := "work-account"
ext := workflow.BuildConnectorExtensions(
workflow.ConnectorSlot{ConnectorName: "gmail"},
workflow.ConnectorSlot{ConnectorName: "notion", CredentialsName: &creds},
)
b, err := json.Marshal(ext)
if err != nil {
t.Fatal(err)
}
want := `{"mistralai":{"connectors":{"bindings":[{"connector_name":"gmail"},{"connector_name":"notion","credentials_name":"work-account"}]}}}`
if string(b) != want {
t.Errorf("\nwant %s\ngot %s", want, string(b))
}
}
func TestExecutionRequest_Extensions(t *testing.T) {
req := workflow.ExecutionRequest{
Extensions: workflow.BuildConnectorExtensions(workflow.ConnectorSlot{ConnectorName: "gmail"}),
}
b, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
var got map[string]any
if err := json.Unmarshal(b, &got); err != nil {
t.Fatal(err)
}
if _, ok := got["extensions"]; !ok {
t.Fatalf("expected extensions key in marshalled request: %s", string(b))
}
}
func TestConnectorAuthTaskState_Roundtrip(t *testing.T) {
authURL := "https://oauth.example.com/authorize"
in := workflow.ConnectorAuthTaskState{
ConnectorName: "gmail",
ConnectorID: "conn-1",
Status: workflow.ConnectorAuthWaitingForAuth,
AuthURL: &authURL,
}
b, err := json.Marshal(in)
if err != nil {
t.Fatal(err)
}
var out workflow.ConnectorAuthTaskState
if err := json.Unmarshal(b, &out); err != nil {
t.Fatal(err)
}
if out.Status != workflow.ConnectorAuthWaitingForAuth {
t.Errorf("got status %q", out.Status)
}
if out.AuthURL == nil || *out.AuthURL != authURL {
t.Errorf("auth_url roundtrip failed")
}
}
func TestConfirmationConstants_WireValues(t *testing.T) {
// Reply constants.
c := conversation.ToolCallConfirmation{
ToolCallID: "call_1",
Confirmation: string(conversation.ConfirmationAllow),
}
b, _ := json.Marshal(c)
if string(b) != `{"tool_call_id":"call_1","confirmation":"allow"}` {
t.Errorf("got %s", string(b))
}
// Inbound status constants.
if conversation.ConfirmationStatusPending != "pending" ||
conversation.ConfirmationStatusAllowed != "allowed" ||
conversation.ConfirmationStatusDenied != "denied" {
t.Errorf("unexpected confirmation status constants")
}
}

31
workflows_metrics.go Normal file
View File

@@ -0,0 +1,31 @@
package mistral
import (
"context"
"fmt"
"net/url"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// GetWorkflowMetrics retrieves performance metrics for a workflow.
func (c *Client) GetWorkflowMetrics(ctx context.Context, workflowName string, params *workflow.MetricsParams) (*workflow.Metrics, error) {
path := fmt.Sprintf("/v1/workflows/%s/metrics", workflowName)
if params != nil {
q := url.Values{}
if params.StartTime != nil {
q.Set("start_time", *params.StartTime)
}
if params.EndTime != nil {
q.Set("end_time", *params.EndTime)
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
var resp workflow.Metrics
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}

44
workflows_metrics_test.go Normal file
View File

@@ -0,0 +1,44 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
func TestGetWorkflowMetrics_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/my-flow/metrics" {
t.Errorf("got path %s", r.URL.Path)
}
if r.URL.Query().Get("start_time") != "2026-01-01T00:00:00Z" {
t.Errorf("got start_time %q", r.URL.Query().Get("start_time"))
}
json.NewEncoder(w).Encode(map[string]any{
"execution_count": map[string]any{"value": 100},
"success_count": map[string]any{"value": 95},
"error_count": map[string]any{"value": 5},
"average_latency_ms": map[string]any{"value": 1234.5},
"latency_over_time": map[string]any{"value": [][]float64{{1711929600, 1200}, {1711929660, 1300}}},
"retry_rate": map[string]any{"value": 0.02},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
start := "2026-01-01T00:00:00Z"
resp, err := client.GetWorkflowMetrics(context.Background(), "my-flow", &workflow.MetricsParams{StartTime: &start})
if err != nil {
t.Fatal(err)
}
if resp.ExecutionCount.Value != 100 {
t.Errorf("got execution_count %v", resp.ExecutionCount.Value)
}
if resp.AverageLatencyMs.Value != 1234.5 {
t.Errorf("got average_latency_ms %v", resp.AverageLatencyMs.Value)
}
}

60
workflows_runs.go Normal file
View File

@@ -0,0 +1,60 @@
package mistral
import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// ListWorkflowRuns lists workflow runs.
func (c *Client) ListWorkflowRuns(ctx context.Context, params *workflow.RunListParams) (*workflow.ListRunsResponse, error) {
path := "/v1/workflows/runs"
if params != nil {
q := url.Values{}
if params.WorkflowIdentifier != nil {
q.Set("workflow_identifier", *params.WorkflowIdentifier)
}
if params.Search != nil {
q.Set("search", *params.Search)
}
if params.Status != nil {
q.Set("status", *params.Status)
}
if params.PageSize != nil {
q.Set("page_size", strconv.Itoa(*params.PageSize))
}
if params.NextPageToken != nil {
q.Set("next_page_token", *params.NextPageToken)
}
if encoded := q.Encode(); encoded != "" {
path += "?" + encoded
}
}
var resp workflow.ListRunsResponse
if err := c.doJSON(ctx, "GET", path, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflowRun retrieves a workflow run by ID.
func (c *Client) GetWorkflowRun(ctx context.Context, runID string) (*workflow.Run, error) {
var resp workflow.Run
if err := c.doJSON(ctx, "GET", fmt.Sprintf("/v1/workflows/runs/%s", runID), nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// GetWorkflowRunHistory retrieves the history of a workflow run.
func (c *Client) GetWorkflowRunHistory(ctx context.Context, runID string) (json.RawMessage, error) {
var resp json.RawMessage
if err := c.doJSON(ctx, "GET", fmt.Sprintf("/v1/workflows/runs/%s/history", runID), nil, &resp); err != nil {
return nil, err
}
return resp, nil
}

58
workflows_runs_test.go Normal file
View File

@@ -0,0 +1,58 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestListWorkflowRuns_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/runs" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"runs": []map[string]any{
{"id": "run-1", "workflow_name": "my-flow", "execution_id": "exec-1", "status": "COMPLETED", "start_time": "2026-01-01"},
},
"next_page_token": "tok-1",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.ListWorkflowRuns(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
if len(resp.Runs) != 1 {
t.Fatalf("got %d runs", len(resp.Runs))
}
if resp.NextPageToken == nil || *resp.NextPageToken != "tok-1" {
t.Errorf("got token %v", resp.NextPageToken)
}
}
func TestGetWorkflowRun_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/runs/run-1" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"id": "run-1", "workflow_name": "my-flow", "execution_id": "exec-1",
"status": "COMPLETED", "start_time": "2026-01-01",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
run, err := client.GetWorkflowRun(context.Background(), "run-1")
if err != nil {
t.Fatal(err)
}
if run.ID != "run-1" {
t.Errorf("got id %q", run.ID)
}
}

39
workflows_schedules.go Normal file
View File

@@ -0,0 +1,39 @@
package mistral
import (
"context"
"fmt"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// ListWorkflowSchedules lists workflow schedules.
func (c *Client) ListWorkflowSchedules(ctx context.Context) (*workflow.ScheduleListResponse, error) {
var resp workflow.ScheduleListResponse
if err := c.doJSON(ctx, "GET", "/v1/workflows/schedules", nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// ScheduleWorkflow creates a workflow schedule.
func (c *Client) ScheduleWorkflow(ctx context.Context, req *workflow.ScheduleRequest) (*workflow.ScheduleResponse, error) {
var resp workflow.ScheduleResponse
if err := c.doJSON(ctx, "POST", "/v1/workflows/schedules", req, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// UnscheduleWorkflow removes a workflow schedule.
func (c *Client) UnscheduleWorkflow(ctx context.Context, scheduleID string) error {
resp, err := c.do(ctx, "DELETE", fmt.Sprintf("/v1/workflows/schedules/%s", scheduleID), nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
return parseAPIError(resp)
}
return nil
}

View File

@@ -0,0 +1,89 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
func TestScheduleWorkflow_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows/schedules" {
t.Errorf("got path %s", r.URL.Path)
}
var body map[string]any
json.NewDecoder(r.Body).Decode(&body)
schedule, _ := body["schedule"].(map[string]any)
cronExprs, _ := schedule["cron_expressions"].([]any)
if len(cronExprs) != 1 || cronExprs[0] != "0 9 * * MON-FRI" {
t.Errorf("got cron_expressions %v", cronExprs)
}
json.NewEncoder(w).Encode(map[string]any{"schedule_id": "sched-1"})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
wfID := "wf-1"
resp, err := client.ScheduleWorkflow(context.Background(), &workflow.ScheduleRequest{
WorkflowIdentifier: &wfID,
Schedule: workflow.ScheduleDefinition{
Input: map[string]any{"prompt": "daily report"},
CronExpressions: []string{"0 9 * * MON-FRI"},
},
})
if err != nil {
t.Fatal(err)
}
if resp.ScheduleID != "sched-1" {
t.Errorf("got schedule_id %q", resp.ScheduleID)
}
}
func TestUnscheduleWorkflow_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows/schedules/sched-1" {
t.Errorf("got path %s", r.URL.Path)
}
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
err := client.UnscheduleWorkflow(context.Background(), "sched-1")
if err != nil {
t.Fatal(err)
}
}
func TestListWorkflowSchedules_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/schedules" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"schedules": []map[string]any{
{"schedule_id": "sched-1", "workflow_name": "my-flow", "created_at": "2026-01-01", "updated_at": "2026-01-01"},
},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.ListWorkflowSchedules(context.Background())
if err != nil {
t.Fatal(err)
}
if len(resp.Schedules) != 1 {
t.Fatalf("got %d schedules", len(resp.Schedules))
}
}

190
workflows_test.go Normal file
View File

@@ -0,0 +1,190 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
func TestListWorkflows_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows" {
t.Errorf("got path %s", r.URL.Path)
}
if r.URL.Query().Get("limit") != "10" {
t.Errorf("got limit %q", r.URL.Query().Get("limit"))
}
if r.URL.Query().Get("active_only") != "true" {
t.Errorf("got active_only %q", r.URL.Query().Get("active_only"))
}
json.NewEncoder(w).Encode(map[string]any{
"workflows": []map[string]any{
{"id": "wf-1", "name": "my-flow", "owner_id": "u1", "workspace_id": "ws1", "created_at": "2026-01-01", "updated_at": "2026-01-01"},
},
"next_cursor": "cur-abc",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
active := true
limit := 10
resp, err := client.ListWorkflows(context.Background(), &workflow.WorkflowListParams{
ActiveOnly: &active,
Limit: &limit,
})
if err != nil {
t.Fatal(err)
}
if len(resp.Workflows) != 1 {
t.Fatalf("got %d workflows", len(resp.Workflows))
}
if resp.Workflows[0].ID != "wf-1" {
t.Errorf("got id %q", resp.Workflows[0].ID)
}
if resp.NextCursor == nil || *resp.NextCursor != "cur-abc" {
t.Errorf("got cursor %v", resp.NextCursor)
}
}
func TestGetWorkflow_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/wf-1" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"id": "wf-1", "name": "my-flow", "owner_id": "u1", "workspace_id": "ws1",
"created_at": "2026-01-01", "updated_at": "2026-01-01",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
wf, err := client.GetWorkflow(context.Background(), "wf-1")
if err != nil {
t.Fatal(err)
}
if wf.Name != "my-flow" {
t.Errorf("got name %q", wf.Name)
}
}
func TestUpdateWorkflow_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
t.Errorf("got method %s", r.Method)
}
var body map[string]any
json.NewDecoder(r.Body).Decode(&body)
if body["display_name"] != "New Name" {
t.Errorf("got display_name %v", body["display_name"])
}
json.NewEncoder(w).Encode(map[string]any{
"id": "wf-1", "name": "my-flow", "display_name": "New Name",
"owner_id": "u1", "workspace_id": "ws1",
"created_at": "2026-01-01", "updated_at": "2026-01-02",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
name := "New Name"
wf, err := client.UpdateWorkflow(context.Background(), "wf-1", &workflow.WorkflowUpdateRequest{
DisplayName: &name,
})
if err != nil {
t.Fatal(err)
}
if wf.DisplayName == nil || *wf.DisplayName != "New Name" {
t.Errorf("got display_name %v", wf.DisplayName)
}
}
func TestArchiveWorkflow_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows/wf-1/archive" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{"id": "wf-1", "archived": true})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.ArchiveWorkflow(context.Background(), "wf-1")
if err != nil {
t.Fatal(err)
}
if !resp.Archived {
t.Error("expected archived=true")
}
}
func TestExecuteWorkflow_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("got method %s", r.Method)
}
if r.URL.Path != "/v1/workflows/wf-1/execute" {
t.Errorf("got path %s", r.URL.Path)
}
var body map[string]any
json.NewDecoder(r.Body).Decode(&body)
input, _ := body["input"].(map[string]any)
if input["prompt"] != "hello" {
t.Errorf("got input %v", body["input"])
}
json.NewEncoder(w).Encode(map[string]any{
"workflow_name": "my-flow", "execution_id": "exec-1",
"root_execution_id": "exec-1", "status": "RUNNING",
"start_time": "2026-01-01T00:00:00Z",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.ExecuteWorkflow(context.Background(), "wf-1", &workflow.ExecutionRequest{
Input: map[string]any{"prompt": "hello"},
})
if err != nil {
t.Fatal(err)
}
if resp.ExecutionID != "exec-1" {
t.Errorf("got execution_id %q", resp.ExecutionID)
}
if resp.Status != workflow.ExecutionRunning {
t.Errorf("got status %q", resp.Status)
}
}
func TestListWorkflowRegistrations_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/workflows/registrations" {
t.Errorf("got path %s", r.URL.Path)
}
json.NewEncoder(w).Encode(map[string]any{
"registrations": []map[string]any{
{"id": "reg-1", "workflow_id": "wf-1", "task_queue": "default", "created_at": "2026-01-01", "updated_at": "2026-01-01"},
},
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
resp, err := client.ListWorkflowRegistrations(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
if len(resp.Registrations) != 1 {
t.Fatalf("got %d registrations", len(resp.Registrations))
}
}

20
workflows_workers.go Normal file
View File

@@ -0,0 +1,20 @@
package mistral
import (
"context"
"github.com/VikingOwl91/mistral-go-sdk/workflow"
)
// GetWorkflowWorkerInfo returns the scheduler URL, namespace, and TLS setting
// the API expects custom workers to connect with.
//
// Most callers using managed deployments do not need this — see
// Registration.DeploymentID. It is exposed for users running custom workers.
func (c *Client) GetWorkflowWorkerInfo(ctx context.Context) (*workflow.WorkerInfo, error) {
var resp workflow.WorkerInfo
if err := c.doJSON(ctx, "GET", "/v1/workflows/workers/whoami", nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}

52
workflows_workers_test.go Normal file
View File

@@ -0,0 +1,52 @@
package mistral
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetWorkflowWorkerInfo_Success(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" || r.URL.Path != "/v1/workflows/workers/whoami" {
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"scheduler_url": "scheduler.example.com:7233",
"namespace": "tenant-2",
"tls": true,
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
info, err := client.GetWorkflowWorkerInfo(context.Background())
if err != nil {
t.Fatal(err)
}
if info.SchedulerURL != "scheduler.example.com:7233" || info.Namespace != "tenant-2" || !info.TLS {
t.Errorf("unexpected info: %+v", info)
}
}
func TestGetWorkflowWorkerInfo_TLSDefault(t *testing.T) {
// Server omits the tls field; the SDK should default it to false.
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"scheduler_url": "s",
"namespace": "n",
})
}))
defer server.Close()
client := NewClient("key", WithBaseURL(server.URL))
info, err := client.GetWorkflowWorkerInfo(context.Background())
if err != nil {
t.Fatal(err)
}
if info.TLS {
t.Errorf("expected default tls=false, got true")
}
}